import type Konva from "konva";
import { useState, useRef, forwardRef, useImperativeHandle, useEffect, useMemo, useCallback } from "react";
import { Group, Shape } from "react-konva";
import {
  ControlPoints,
  GhostLine,
  GhostPoint,
  type GhostPointRef,
  VectorPoints,
  VectorShape,
  VectorTransformer,
  ProxyNodes,
} from "./components";
import { createEventHandlers } from "./eventHandlers";
import { convertPoint } from "./pointManagement";
import { normalizePoints, convertBezierToSimplePoints, isPointInPolygon } from "./utils";
import { findClosestPointOnPath, getDistance, snapToPixel } from "./eventHandlers/utils";
import { constrainAnchorPointsToBounds, constrainPointToBounds } from "./utils/boundsChecking";
import { stageToImageCoordinates } from "./eventHandlers/utils";
import { PointCreationManager } from "./pointCreationManager";
import { VectorSelectionTracker, type VectorInstance } from "./VectorSelectionTracker";
import { calculateShapeBoundingBox } from "./utils/bezierBoundingBox";
import {
  shouldClosePathOnPointClick,
  isActivePointEligibleForClosing,
  handlePointDeselection,
  handlePointSelection,
} from "./eventHandlers/pointSelection";
import { handlePointSelectionFromIndex } from "./eventHandlers/mouseHandlers";
import { handleShiftClickPointConversion } from "./eventHandlers/drawing";
import { deletePoint } from "./pointManagement";
import type { BezierPoint, GhostPoint as GhostPointType, KonvaVectorProps, KonvaVectorRef } from "./types";
import { ShapeType, ExportFormat, PathType } from "./types";
import {
  INSTANCE_ID_PREFIX,
  INSTANCE_ID_LENGTH,
  DEFAULT_TRANSFORM,
  DEFAULT_FIT_SCALE,
  DEFAULT_TRANSFORMER_STATE,
  DEFAULT_STROKE_COLOR,
  DEFAULT_FILL_COLOR,
  DEFAULT_POINT_FILL,
  DEFAULT_POINT_STROKE,
  DEFAULT_POINT_STROKE_SELECTED,
  DEFAULT_POINT_STROKE_WIDTH,
  HIT_RADIUS,
  TRANSFORMER_SETUP_DELAY,
  TRANSFORMER_CLEAR_DELAY,
  MIN_POINTS_FOR_CLOSING,
  MIN_POINTS_FOR_BEZIER_CLOSING,
  INVISIBLE_SHAPE_OPACITY,
  DEGREES_TO_RADIANS,
  DEFAULT_SCALE,
  DEFAULT_OFFSET,
  DEFAULT_CALLBACK_TIME,
  ARRAY_INDEX,
  SELECTION_SIZE,
  CENTER_CALCULATION_DIVISOR,
} from "./constants";

/**
 * **KonvaVector Component** - Advanced vector graphics editor with bezier curve support
 *
 * A comprehensive React component for creating and editing polylines and polygons with support for:
 * - **Dual Point Formats**: Simple `[[x,y],...]` or complex `{x,y,isBezier,...}` formats
 * - **Bezier Curves**: Create smooth curves with control points and conversion between regular/bezier points
 * - **Interactive Editing**: Point selection, dragging, multi-selection with transformer
 * - **Drawing Modes**: Click-to-add points, drag-to-create bezier curves, ghost point insertion
 * - **Path Management**: Open polylines or closed polygons with configurable constraints
 * - **Export Formats**: Export as simple coordinates or detailed format with bezier information
 *
 * ## Key Features
 *
 * ### Point Management
 * - **Add Points**: Click in drawing mode, Shift+click on path segments
 * - **Edit Points**: Drag to reposition, Alt+click to convert regular ↔ bezier
 * - **Delete Points**: Alt+click on existing points
 * - **Multi-Selection**: Select multiple points for batch transformations
 * - **Break Closed Path**: Alt+click on any segment of a closed path to break it at that specific segment (array is shifted so breaking point becomes first, point before breaking becomes active)
 *
 * ### Bezier Curves
 * - **Create**: Drag while adding points or convert existing points
 * - **Edit**: Drag control points, disconnect/reconnect control handles
 * - **Control**: `allowBezier` prop to enable/disable bezier functionality
 *
 * ### Interaction Modes
 * - **Drawing Mode**: `isDrawingMode={true}` - Click to add points, drag for bezier curves
 * - **Edit Mode**: `isDrawingMode={false}` - Select, drag, and transform existing points
 * - **Skeleton Mode**: `skeletonEnabled={true}` - Connect points to active point instead of last point
 * - **Disabled Mode**: Point selection still works for keypoint annotation - click to select points, Cmd/Ctrl+click for multi-selection
 *
 * ### Export & Import
 * - **Simple Format**: `[[x,y], [x,y], ...]` - Easy to work with
 * - **Complex Format**: `[{x,y,isBezier,controlPoint1,controlPoint2}, ...]` - Full feature support
 * - **Auto Type Detection**: Exports include `type: ShapeType.POLYGON | ShapeType.POLYLINE` based on `allowClose`
 *
 * ## Usage Examples
 *
 * ### Basic Vector Path
 * ```tsx
 * <KonvaVector
 *   initialPoints={EXAMPLE_COORDINATES.BASIC_PATH}
 *   onPointsChange={setPoints}
 *   isDrawingMode={true}
 *   allowClose={false}
 * />
 * ```
 *
 * ### Polygon with Bezier Support
 * ```tsx
 * <KonvaVector
 *   initialPoints={complexPoints}
 *   onPointsChange={setPoints}
 *   allowClose={true}
 *   allowBezier={true}
 *   minPoints={EXAMPLE_POINT_CONSTRAINTS.MIN_POINTS}
 *   maxPoints={EXAMPLE_POINT_CONSTRAINTS.MAX_POINTS}
 * />
 * ```
 *
 * ### With Export Handling
 * ```tsx
 * <KonvaVector
 *   initialPoints={points}
 *   onPointsChange={setPoints}
 *   format={ExportFormat.SIMPLE}
 *   onTransformationComplete={(data) => {

 *   }}
 * />
 * ```
 *
 * ### With Path Breaking
 * ```tsx
 * <KonvaVector
 *   initialPoints={closedPolygonPoints}
 *   onPointsChange={setPoints}
 *   allowClose={true}
 *   closed={true}
 *   // Alt+click on any segment to break the closed path at that specific segment
 *   // The points array is shifted so the breaking point becomes the first element, point before breaking becomes active
 *   onPathClosedChange={(isClosed) => {
 *     console.log('Path closed state:', isClosed);
 *   }}
 * />
 * ```
 *
 * ### With Custom Point Styling
 * ```tsx
 * <KonvaVector
 *   initialPoints={points}
 *   onPointsChange={setPoints}
 *   // Customize point appearance
 *   pointRadius={DEFAULT_POINT_RADIUS}
 *   pointFill={DEFAULT_POINT_FILL}
 *   pointStroke={DEFAULT_POINT_STROKE}
 *   pointStrokeSelected={DEFAULT_POINT_STROKE_SELECTED}
 *   pointStrokeWidth={DEFAULT_POINT_STROKE_WIDTH}
 * />
 * ```
 *
 * ### As Keypoint Annotation Tool
 * ```tsx
 * <KonvaVector
 *   initialPoints={keypoints}
 *   onPointsChange={setKeypoints}
 *   selected={false} // Disable editing but allow selection
 *   allowClose={false} // Keypoints don't form closed paths
 *   allowBezier={false} // Keypoints are simple points
 *   pointRadius={KEYPOINT_POINT_RADIUS}
 *   onPointSelected={(pointIndex) => {
 *     console.log('Selected keypoint:', pointIndex);
 *   }}
 * />
 * ```
 *
 * ## Keyboard Shortcuts
 * - **Alt + Click**: Convert point between regular ↔ bezier
 * - **Alt + Click on segment**: Break closed path at segment (when path is closed)
 * - **Click on first/last point**: Close path bidirectionally (first→last or last→first)
 * - **Shift + Click**: Add point on path segment
 * - **Shift + Drag**: Create bezier point with control handles
 *
 * ## Props Overview
 * - `initialPoints`: Points in simple or complex format
 * - `allowBezier`: Enable/disable bezier curve functionality
 * - `allowClose`: Allow path to be closed into polygon
 * - `isDrawingMode`: Enable point addition mode
 * - `skeletonEnabled`: Connect new points to active point
 * - `format`: Export format (`ExportFormat.SIMPLE` | `ExportFormat.REGULAR`)
 * - `minPoints`/`maxPoints`: Point count constraints
 * - `stroke`/`fill`: Path styling colors
 * - `pointRadius`: Configurable point sizes for enabled/disabled states
 * - `pointFill`/`pointStroke`/`pointStrokeSelected`: Point styling colors
 * - `pointStrokeWidth`: Point stroke width
 * - Event handlers for all point operations
 *
 * @component
 * @example
 * ```tsx
 * // Simple usage
 * <KonvaVector
 *   initialPoints={EXAMPLE_COORDINATES.SIMPLE_PATH}
 *   onPointsChange={handlePointsChange}
 *   allowBezier={true}
 *   allowClose={false}
 * />
 * ```
 */
export const KonvaVector = forwardRef<KonvaVectorRef, KonvaVectorProps>((props, ref) => {
  // Generate unique instance ID
  const instanceId = useMemo(
    () => `${INSTANCE_ID_PREFIX}${Math.random().toString(36).substr(2, INSTANCE_ID_LENGTH)}`,
    [],
  );

  // Flag to track programmatic selection to prevent infinite loops
  const isProgrammaticSelection = useRef(false);

  const {
    initialPoints: rawInitialPoints = [],
    onPointsChange,
    onPointAdded,
    onPointRemoved,
    onPointEdited,
    onPointRepositioned,
    onPointConverted,
    onPathShapeChanged,
    onPathClosedChange,
    onTransformationComplete,
    onPointSelected,
    onFinish,
    onGhostPointClick,
    scaleX,
    scaleY,
    x,
    y,
    imageSmoothingEnabled = false,
    transform = DEFAULT_TRANSFORM,
    fitScale = DEFAULT_FIT_SCALE,
    width,
    height,
    onMouseDown,
    onMouseMove,
    onMouseUp,
    onClick,
    onDblClick,
    onTransformStart,
    onTransformEnd,
    onMouseEnter,
    onMouseLeave,
    allowClose = false,
    closed,
    allowBezier = true,
    minPoints,
    maxPoints,
    skeletonEnabled = false,
    format = ExportFormat.REGULAR,
    stroke = DEFAULT_STROKE_COLOR,
    fill = DEFAULT_FILL_COLOR,
    pixelSnapping = false,
    selected = true,
    disabled = false,
    transformMode = false,
    isMultiRegionSelected = false,
    disableInternalPointAddition = false,
    disableGhostLine = false,
    pointRadius,
    pointFill = DEFAULT_POINT_FILL,
    pointStroke = DEFAULT_POINT_STROKE,
    pointStrokeSelected = DEFAULT_POINT_STROKE_SELECTED,
    pointStrokeWidth = DEFAULT_POINT_STROKE_WIDTH,
  } = props;

  // Normalize input points to BezierPoint format
  const [initialPoints, setInitialPoints] = useState(() => normalizePoints(rawInitialPoints));

  // Ref to track current points for immediate access during transformation
  // This ensures applyTransformationToPoints always uses the latest points
  const currentPointsRef = useRef<BezierPoint[]>(initialPoints);

  // Update ref whenever initialPoints state changes
  useEffect(() => {
    currentPointsRef.current = initialPoints;
  }, [initialPoints]);

  // Ref to track if we're updating points internally to prevent infinite loops
  const isInternalUpdateRef = useRef(false);
  // Ref to track the last normalized points to prevent unnecessary updates
  const lastNormalizedPointsRef = useRef<BezierPoint[]>(initialPoints);
  // Ref to track the last points we sent to parent to detect circular updates
  const lastSentToParentRef = useRef<BezierPoint[] | null>(null);

  // Helper function to compare points by their actual data (ignoring IDs)
  const arePointsEqual = useCallback((points1: BezierPoint[], points2: BezierPoint[]): boolean => {
    if (points1.length !== points2.length) return false;

    for (let i = 0; i < points1.length; i++) {
      const p1 = points1[i];
      const p2 = points2[i];

      if (
        p1.x !== p2.x ||
        p1.y !== p2.y ||
        p1.isBezier !== p2.isBezier ||
        p1.disconnected !== p2.disconnected ||
        p1.isBranching !== p2.isBranching
      ) {
        return false;
      }

      // Compare control points if bezier
      if (p1.isBezier) {
        if (!!p1.controlPoint1 !== !!p2.controlPoint1) return false;
        if (!!p1.controlPoint2 !== !!p2.controlPoint2) return false;

        if (p1.controlPoint1 && p2.controlPoint1) {
          if (p1.controlPoint1.x !== p2.controlPoint1.x || p1.controlPoint1.y !== p2.controlPoint1.y) {
            return false;
          }
        }

        if (p1.controlPoint2 && p2.controlPoint2) {
          if (p1.controlPoint2.x !== p2.controlPoint2.x || p1.controlPoint2.y !== p2.controlPoint2.y) {
            return false;
          }
        }
      }
    }

    return true;
  }, []);

  // Update initialPoints when rawInitialPoints changes (only if different)
  useEffect(() => {
    // Skip if this is an internal update
    if (isInternalUpdateRef.current) {
      return;
    }

    const normalized = normalizePoints(rawInitialPoints);

    // Skip if this matches what we just sent to parent (circular update prevention)
    if (lastSentToParentRef.current && arePointsEqual(lastSentToParentRef.current, normalized)) {
      lastSentToParentRef.current = null; // Clear after handling
      return;
    }

    // Only update if points actually changed (compare by data, not IDs)
    if (!arePointsEqual(lastNormalizedPointsRef.current, normalized)) {
      lastNormalizedPointsRef.current = normalized;
      setInitialPoints(normalized);
    }
  }, [rawInitialPoints, arePointsEqual]);

  // Initialize lastAddedPointId and activePointId when component loads with existing points
  useEffect(() => {
    if (initialPoints.length > 0) {
      const lastPoint = initialPoints[initialPoints.length - 1];
      setLastAddedPointId(lastPoint.id);
      // Only set activePointId if skeleton mode is enabled
      if (skeletonEnabled) {
        setActivePointId(lastPoint.id);
      }
    }
  }, [initialPoints.length, skeletonEnabled]); // Only run when the number of points changes or skeleton mode changes

  // Use initialPoints directly - this will update when the parent re-renders
  const [selectedPointIndex, setSelectedPointIndex] = useState<number | null>(null);
  const [selectedPoints, setSelectedPoints] = useState<Set<number>>(new Set());

  // Compute effective selected points - when transformMode is true, all points are selected
  const effectiveSelectedPoints = useMemo(() => {
    if (transformMode && initialPoints.length > 0) {
      return new Set(Array.from({ length: initialPoints.length }, (_, i) => i));
    }
    return selectedPoints;
  }, [transformMode, initialPoints.length, selectedPoints]);
  const [lastAddedPointId, setLastAddedPointId] = useState<string | null>(null);

  const transformerRef = useRef<Konva.Transformer>(null);
  const stageRef = useRef<Konva.Layer>(null);
  const pointRefs = useRef<{ [key: number]: Konva.Circle | null }>({});
  const proxyRefs = useRef<{ [key: number]: Konva.Circle | null }>({});
  // Store transformer state to preserve rotation, scale, and center when updating selection
  const transformerStateRef = useRef<{
    rotation: number;
    scaleX: number;
    scaleY: number;
    centerX: number;
    centerY: number;
  }>(DEFAULT_TRANSFORMER_STATE);

  // Handle Shift key state
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.shiftKey) {
        setIsShiftKeyHeld(true);
        setIsDisconnectedMode(true);
        // Recalculate ghost point when Shift is pressed
        // Use a small delay to ensure state is updated
        setTimeout(() => {
          if (calculateGhostPointRef.current) {
            // Pass true for shiftKeyState since Shift is being pressed
            calculateGhostPointRef.current(true);
          }
        }, 0);
      }
    };

    const handleKeyUp = (e: KeyboardEvent) => {
      if (!e.shiftKey) {
        setIsShiftKeyHeld(false);
        setIsDisconnectedMode(false);
        // Clear ghost point when Shift is released
        setGhostPoint(null);
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
    };
  }, []);

  const [draggedPointIndex, setDraggedPointIndex] = useState<number | null>(null);
  const [isShiftKeyHeld, setIsShiftKeyHeld] = useState(false);
  const [draggedControlPoint, setDraggedControlPoint] = useState<{
    pointIndex: number;
    controlIndex: number;
  } | null>(null);
  const [isDraggingShape, setIsDraggingShape] = useState(false);
  const shapeDragStartPos = useRef<{ x: number; y: number; imageX: number; imageY: number } | null>(null);
  const originalPointsPositions = useRef<
    Array<{ x: number; y: number; controlPoint1?: { x: number; y: number }; controlPoint2?: { x: number; y: number } }>
  >([]);
  const justFinishedShapeDrag = useRef(false);
  const shapeDragDistance = useRef(0);
  const [isDisconnectedMode, setIsDisconnectedMode] = useState(false);
  const [ghostPoint, setGhostPoint] = useState<GhostPointType | null>(null);

  // Clear ghost point when conditions change that should hide it
  useEffect(() => {
    // Clear ghost point when:
    // - Shape is disabled
    // - Shape is not selected
    // - Max points reached
    // Note: Shift key release is handled in handleKeyUp, not here
    if (disabled || !selected || (maxPoints !== undefined && initialPoints.length >= maxPoints)) {
      setGhostPoint(null);
    }
  }, [disabled, selected, maxPoints, initialPoints.length]);

  const [_newPointDragIndex, setNewPointDragIndex] = useState<number | null>(null);
  const [isDraggingNewBezier, setIsDraggingNewBezier] = useState(false);
  const [ghostPointDragInfo, setGhostPointDragInfo] = useState<{
    ghostPoint: GhostPointType;
    isDragging: boolean;
    dragDistance: number;
  } | null>(null);

  const cursorPositionRef = useRef<{
    x: number;
    y: number;
  } | null>(null);
  const ghostLineRafRef = useRef<number | null>(null);
  const lastCallbackTime = useRef<number>(DEFAULT_CALLBACK_TIME);
  const [visibleControlPoints, setVisibleControlPoints] = useState<Set<number>>(new Set());
  const [activePointId, setActivePointId] = useState<string | null>(null);
  const [isTransforming, setIsTransforming] = useState(false);

  // Flag to track if point selection was handled in VectorPoints onClick
  const pointSelectionHandled = useRef(false);

  // Ref to track the _transformable group for applying transformations
  const transformableGroupRef = useRef<Konva.Group>(null);

  // Ref to track click timeout for click/double-click debouncing
  const clickTimeoutRef = useRef<number | null>(null);

  // Flag to track if we've handled a double-click through debouncing
  const doubleClickHandledRef = useRef(false);

  // Track if we're currently transforming to avoid duplicate onTransformStart calls
  const isTransformingRef = useRef(false);

  // Helper function to call onTransformStart (only if not already transforming)
  const handleTransformStart = useCallback(() => {
    if (!isTransformingRef.current) {
      isTransformingRef.current = true;
      // Always call onTransformStart if provided, even if called multiple times
      // The flag prevents duplicate calls, but we ensure it's called at least once
      onTransformStart?.();
    }
  }, [onTransformStart]);

  // Helper function to call onTransformEnd (only if currently transforming)
  const handleTransformEnd = useCallback(
    (e?: Konva.KonvaEventObject<MouseEvent>) => {
      // Always reset the flag if we were transforming
      // This ensures we don't get stuck in a transforming state
      if (isTransformingRef.current) {
        isTransformingRef.current = false;
        // Always call onTransformEnd if provided to ensure history is unfrozen
        onTransformEnd?.(e);
      }
    },
    [onTransformEnd],
  );

  // Track initial transform state for delta calculation
  const initialTransformRef = useRef<{
    x: number;
    y: number;
    scaleX: number;
    scaleY: number;
    rotation: number;
  } | null>(null);

  // Define commitMultiRegionTransform as a useCallback so we can use it in both useImperativeHandle and onDragEnd
  const commitMultiRegionTransform = useCallback(() => {
    if (!isMultiRegionSelected || !transformableGroupRef.current || !initialTransformRef.current) {
      return;
    }

    // Get the _transformable group
    const transformableGroup = transformableGroupRef.current;

    // Get the group's current transform values
    const currentX = transformableGroup.x();
    const currentY = transformableGroup.y();
    const currentScaleX = transformableGroup.scaleX();
    const currentScaleY = transformableGroup.scaleY();
    const currentRotation = transformableGroup.rotation();

    // Calculate deltas from initial state
    const initial = initialTransformRef.current;
    const dx = currentX - initial.x;
    const dy = currentY - initial.y;
    const scaleX = currentScaleX / initial.scaleX;
    const scaleY = currentScaleY / initial.scaleY;
    const rotation = currentRotation - initial.rotation;

    // Apply constraints to the transform before committing
    const imageWidth = width || 0;
    const imageHeight = height || 0;

    let constrainedDx = dx;
    let constrainedDy = dy;
    const constrainedScaleX = scaleX;
    const constrainedScaleY = scaleY;

    if (imageWidth > 0 && imageHeight > 0) {
      // Calculate bounding box of current points after transform
      const xs = initialPoints.map((p) => p.x);
      const ys = initialPoints.map((p) => p.y);
      const minX = Math.min(...xs);
      const maxX = Math.max(...xs);
      const minY = Math.min(...ys);
      const maxY = Math.max(...ys);

      // Apply scale and position to get new bounds
      const scaledMinX = minX * scaleX + dx;
      const scaledMaxX = maxX * scaleX + dx;
      const scaledMinY = minY * scaleY + dy;
      const scaledMaxY = maxY * scaleY + dy;

      // Apply constraints
      if (scaledMinX < 0) constrainedDx = dx - scaledMinX;
      if (scaledMaxX > imageWidth) constrainedDx = dx - (scaledMaxX - imageWidth);
      if (scaledMinY < 0) constrainedDy = dy - scaledMinY;
      if (scaledMaxY > imageHeight) constrainedDy = dy - (scaledMaxY - imageHeight);
    }

    // Apply the transformation exactly as the single-region onTransformEnd handler does:
    // 1. Scale around origin (0,0)
    // 2. Rotate around origin (0,0)
    // 3. Translate by (constrainedDx, constrainedDy)
    const radians = rotation * (Math.PI / 180);
    const cos = Math.cos(radians);
    const sin = Math.sin(radians);

    const transformedVertices = initialPoints.map((point) => {
      // Step 1: Scale
      const x = point.x * constrainedScaleX;
      const y = point.y * constrainedScaleY;

      // Step 2: Rotate
      const rx = x * cos - y * sin;
      const ry = x * sin + y * cos;

      // Step 3: Translate and clamp to image bounds
      const translatedPos = {
        x: Math.max(0, Math.min(imageWidth, rx + constrainedDx)),
        y: Math.max(0, Math.min(imageHeight, ry + constrainedDy)),
      };
      // Apply pixel snapping if enabled
      const snappedPos = snapToPixel(translatedPos, pixelSnapping);
      const result = {
        ...point,
        x: snappedPos.x,
        y: snappedPos.y,
      };

      // Transform control points if bezier
      if (point.isBezier) {
        if (point.controlPoint1) {
          const cp1x = point.controlPoint1.x * constrainedScaleX;
          const cp1y = point.controlPoint1.y * constrainedScaleY;
          const cp1rx = cp1x * cos - cp1y * sin;
          const cp1ry = cp1x * sin + cp1y * cos;
          const cp1Translated = {
            x: Math.max(0, Math.min(imageWidth, cp1rx + constrainedDx)),
            y: Math.max(0, Math.min(imageHeight, cp1ry + constrainedDy)),
          };
          result.controlPoint1 = snapToPixel(cp1Translated, pixelSnapping);
        }
        if (point.controlPoint2) {
          const cp2x = point.controlPoint2.x * constrainedScaleX;
          const cp2y = point.controlPoint2.y * constrainedScaleY;
          const cp2rx = cp2x * cos - cp2y * sin;
          const cp2ry = cp2x * sin + cp2y * cos;
          const cp2Translated = {
            x: Math.max(0, Math.min(imageWidth, cp2rx + constrainedDx)),
            y: Math.max(0, Math.min(imageHeight, cp2ry + constrainedDy)),
          };
          result.controlPoint2 = snapToPixel(cp2Translated, pixelSnapping);
        }
      }

      return result;
    });

    // Update the points
    onPointsChange?.(transformedVertices);

    // Reset the _transformable group transform to identity
    // This ensures the visual representation matches the committed data
    transformableGroup.x(0);
    transformableGroup.y(0);
    transformableGroup.scaleX(1);
    transformableGroup.scaleY(1);
    transformableGroup.rotation(0);

    // Update the initial transform state to reflect the reset
    initialTransformRef.current = {
      x: 0,
      y: 0,
      scaleX: 1,
      scaleY: 1,
      rotation: 0,
    };

    // Detach and reattach the transformer to prevent resizing issues
    // BUT: Delay this when multiple regions are selected to allow other regions'
    // onTransformEnd handlers to fire first (e.g., PolygonRegion's Line onTransformEnd)
    const stage = transformableGroup.getStage();
    if (stage) {
      const transformer = stage.findOne("Transformer") as Konva.Transformer | undefined;
      if (transformer) {
        // Check if there are multiple nodes attached (multiple regions selected)
        const nodes = transformer.nodes();
        const hasMultipleRegions = nodes.length > 1;

        // If multiple regions, delay the detach/reattach to allow other onTransformEnd handlers to fire
        const delay = hasMultipleRegions ? 50 : 0;

        setTimeout(() => {
          // Temporarily detach the transformer
          transformer.nodes([]);

          // Force a redraw
          stage.batchDraw();

          // Reattach the transformer after a brief delay
          setTimeout(() => {
            transformer.nodes(nodes);
            stage.batchDraw();
          }, 0);
        }, delay);
      }
    }
  }, [isMultiRegionSelected, initialPoints, width, height, onPointsChange]);

  // Capture initial transform state when group is created
  useEffect(() => {
    if (isMultiRegionSelected && transformableGroupRef.current && !initialTransformRef.current) {
      const group = transformableGroupRef.current;
      initialTransformRef.current = {
        x: group.x(),
        y: group.y(),
        scaleX: group.scaleX(),
        scaleY: group.scaleY(),
        rotation: group.rotation(),
      };
    } else if (!isMultiRegionSelected) {
      // Reset when not in multi-region mode
      initialTransformRef.current = null;
    }
  }, [isMultiRegionSelected]);

  // Initialize PointCreationManager instance
  const pointCreationManager = useMemo(() => new PointCreationManager(), []);

  // Initialize VectorSelectionTracker
  const tracker = useMemo(() => VectorSelectionTracker.getInstance(), []);

  // Compute if path is closed based on point references
  // A path is closed if the first point's prevPointId points to the last point
  const isPathClosed = useMemo(() => {
    if (!allowClose || initialPoints.length < 2) {
      return false;
    }

    const firstPoint = initialPoints[0];
    const lastPoint = initialPoints[initialPoints.length - 1];

    // Path is closed if the first point's prevPointId points to the last point
    return firstPoint.prevPointId === lastPoint.id;
  }, [allowClose, initialPoints]);

  // Use external closed prop when provided, otherwise use computed value
  const finalIsPathClosed = allowClose && closed !== undefined ? closed : isPathClosed;

  // Debug logging for path closure state
  useEffect(() => {
    if (allowClose && initialPoints.length >= 2) {
      const firstPoint = initialPoints[0];
      const lastPoint = initialPoints[initialPoints.length - 1];
    }
  }, [allowClose, initialPoints, isPathClosed, finalIsPathClosed]);

  // Setter for path closed state - used when path is closed/opened programmatically
  const setIsPathClosed = useCallback(
    (closed: boolean) => {
      if (allowClose) {
        // Always notify parent when path closure state changes programmatically
        onPathClosedChange?.(closed);
      }
    },
    [allowClose, onPathClosedChange],
  );

  const isDragging = useRef(false);
  const [stageReadyRetry, setStageReadyRetry] = useState(0);
  const calculateGhostPointRef = useRef<
    ((shiftKeyState?: boolean, eventPos?: { x: number; y: number }) => void) | null
  >(null);
  const ghostPointRef = useRef<GhostPointRef | null>(null);

  // Ref to prevent effect from running multiple times
  const handlersAttachedRef = useRef(false);

  // Refs to access current values in event handlers without recreating them
  const currentValuesRef = useRef({
    initialPoints,
    effectiveSelectedPoints,
    allowClose,
    finalIsPathClosed,
    pixelSnapping,
    isDraggingNewBezier,
    ghostPointDragInfo,
    draggedPointIndex,
    draggedControlPoint,
    isDraggingShape,
    instanceId,
    transform,
    fitScale,
    x,
    y,
    width,
    height,
    skeletonEnabled,
    activePointId,
    lastAddedPointId,
    selected,
    disabled,
    onFinish,
    isShiftKeyHeld,
  });

  // Update refs on every render
  currentValuesRef.current = {
    initialPoints,
    effectiveSelectedPoints,
    allowClose,
    finalIsPathClosed,
    pixelSnapping,
    isDraggingNewBezier,
    ghostPointDragInfo,
    draggedPointIndex,
    draggedControlPoint,
    isDraggingShape,
    instanceId,
    transform,
    fitScale,
    x,
    y,
    width,
    height,
    skeletonEnabled,
    activePointId,
    lastAddedPointId,
    selected,
    disabled,
    onFinish,
    isShiftKeyHeld,
  };

  // Determine if drawing should be disabled based on current interaction context
  const isDrawingDisabled = () => {
    // Disable all interactions when disabled prop is true
    // Disable all interactions when selected prop is false
    // Disable drawing when Shift is held (for Shift+click functionality)
    // Disable drawing when multiple points are selected or when in transform mode
    if (
      disabled ||
      !selected ||
      isShiftKeyHeld ||
      effectiveSelectedPoints.size > SELECTION_SIZE.MULTI_SELECTION_MIN ||
      transformMode
    ) {
      return true;
    }

    // Dynamically check control point hover
    if (cursorPositionRef.current && initialPoints.length > 0) {
      const scale = transform.zoom * fitScale;
      const controlPointHitRadius = HIT_RADIUS.CONTROL_POINT / scale;

      for (let i = 0; i < initialPoints.length; i++) {
        const point = initialPoints[i];
        if (point.isBezier) {
          // Check control point 1
          if (point.controlPoint1) {
            const distance = Math.sqrt(
              (cursorPositionRef.current.x - point.controlPoint1.x) ** 2 +
                (cursorPositionRef.current.y - point.controlPoint1.y) ** 2,
            );
            if (distance <= controlPointHitRadius) {
              return true; // Disable drawing when hovering over control points
            }
          }
          // Check control point 2
          if (point.controlPoint2) {
            const distance = Math.sqrt(
              (cursorPositionRef.current.x - point.controlPoint2.x) ** 2 +
                (cursorPositionRef.current.y - point.controlPoint2.y) ** 2,
            );
            if (distance <= controlPointHitRadius) {
              return true; // Disable drawing when hovering over control points
            }
          }
        }
      }
    }

    // Dynamically check point hover
    if (cursorPositionRef.current && initialPoints.length > 0) {
      const scale = transform.zoom * fitScale;
      const selectionHitRadius = HIT_RADIUS.SELECTION / scale;

      for (let i = 0; i < initialPoints.length; i++) {
        const point = initialPoints[i];
        const distance = Math.sqrt(
          (cursorPositionRef.current.x - point.x) ** 2 + (cursorPositionRef.current.y - point.y) ** 2,
        );
        if (distance <= selectionHitRadius) {
          // If exactly one point is selected and this is that point, allow drawing
          if (selectedPoints.size === SELECTION_SIZE.MULTI_SELECTION_MIN && selectedPoints.has(i)) {
            continue; // Don't disable drawing for the selected point
          }

          // Don't disable drawing when hovering over the last point in the path
          // (so you can continue drawing from it)
          if (i === initialPoints.length - ARRAY_INDEX.LAST_OFFSET) {
            continue; // Don't disable drawing for the last point
          }

          // Don't disable drawing when hovering over the first point if path closing is possible
          // (so you can see the closing indicator)
          if (i === ARRAY_INDEX.FIRST && allowClose && !finalIsPathClosed) {
            continue; // Don't disable drawing for the first point when closing is possible
          }

          return true; // Disable drawing when hovering over other points
        }
      }
    }

    // Dynamically check segment hover (to hide ghost line when hovering over path segments)
    if (cursorPositionRef.current && initialPoints.length >= 2) {
      const scale = transform.zoom * fitScale;
      const segmentHitRadius = HIT_RADIUS.SEGMENT / scale; // Slightly larger than point hit radius

      // Use the same logic as findClosestPointOnPath for consistent Bezier curve detection
      const closestPathPoint = findClosestPointOnPath(
        cursorPositionRef.current,
        initialPoints,
        allowClose,
        finalIsPathClosed,
      );

      if (closestPathPoint && getDistance(cursorPositionRef.current, closestPathPoint.point) <= segmentHitRadius) {
        return true; // Disable drawing when hovering over segments
      }
    }

    return false; // Drawing is enabled
  };

  const drawingDisabled = isDrawingDisabled();

  // Notify parent when path closure state changes (only when not using external state)
  useEffect(() => {
    if (!(allowClose && closed !== undefined)) {
      onPathClosedChange?.(finalIsPathClosed);
    }
  }, [finalIsPathClosed, closed, allowClose, onPathClosedChange]);

  // Handle drawing mode changes
  useEffect(() => {
    if (!drawingDisabled) {
      setVisibleControlPoints(new Set());
    }
  }, [drawingDisabled]);

  // Initialize cursor position when points are available or component becomes active
  // This ensures ghost line can render immediately
  useEffect(() => {
    const group = stageRef.current;
    if (!group) return;

    const stage = group.getStage();
    if (!stage) return;

    // Try to get current mouse position and set cursor position
    const initializeCursorPosition = () => {
      const pos = stage.getPointerPosition();
      if (pos) {
        const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
        // Always update cursor position when points are available (not just when null)
        // This ensures ghost line can render immediately after region selection
        cursorPositionRef.current = imagePos;
        // Trigger a redraw to show ghost line
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        ghostLineRafRef.current = requestAnimationFrame(() => {
          stage.batchDraw();
        });
        return true;
      }
      return false;
    };

    // Try immediately
    const gotPosition = initializeCursorPosition();

    // Only set fallback position if this instance is active/selected and selected
    // Check if this instance is the active one using the tracker
    const isActiveInstance = tracker.getActiveInstanceId() === instanceId;
    const hasSelection = selectedPoints.size > 0 || effectiveSelectedPoints.size > 0;
    const isInstanceSelected = tracker.isInstanceSelected(instanceId);
    // Show ghost line only if not disabled AND selected AND (active OR has selection)
    const shouldShowGhostLine = !disabled && selected && (isActiveInstance || hasSelection || isInstanceSelected);

    // If we couldn't get the position and we have points, set a fallback position
    // Use the last point or center of the region as a fallback until mouse moves
    // Only do this for the active/selected instance that is selected
    if (!gotPosition && initialPoints.length > 0 && !cursorPositionRef.current && shouldShowGhostLine) {
      const lastPoint = initialPoints[initialPoints.length - 1];
      // Set cursor position to a small offset from the last point so ghost line is visible
      cursorPositionRef.current = {
        x: lastPoint.x + 50,
        y: lastPoint.y + 50,
      };
      // Trigger a redraw to show ghost line
      if (ghostLineRafRef.current) {
        cancelAnimationFrame(ghostLineRafRef.current);
      }
      ghostLineRafRef.current = requestAnimationFrame(() => {
        stage.batchDraw();
      });
    } else if (!shouldShowGhostLine && cursorPositionRef.current) {
      // Clear cursor position if this instance shouldn't show ghost line
      cursorPositionRef.current = null;
    }

    // Also try after a short delay to ensure stage is ready
    const timeout = setTimeout(() => {
      if (!cursorPositionRef.current) {
        initializeCursorPosition();
      }
    }, 0);

    // Add a one-time mousemove listener to capture cursor position immediately when mouse moves
    // This ensures we get the position even if getPointerPosition() returns null initially
    const handleOneTimeMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
      const pos = e.target.getStage()?.getPointerPosition();
      if (pos) {
        const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
        cursorPositionRef.current = imagePos;
        // Trigger a redraw to show ghost line
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        ghostLineRafRef.current = requestAnimationFrame(() => {
          stage.batchDraw();
        });
        // Remove listener after first capture
        stage.off("mousemove", handleOneTimeMouseMove);
      }
    };

    // Add one-time listener
    stage.on("mousemove", handleOneTimeMouseMove);

    return () => {
      clearTimeout(timeout);
      stage.off("mousemove", handleOneTimeMouseMove);
    };
  }, [
    initialPoints.length,
    transform,
    fitScale,
    x,
    y,
    selected,
    instanceId,
    selectedPoints.size,
    effectiveSelectedPoints.size,
  ]); // Re-run when points change, transform changes, or selection changes

  // Stabilize functions for tracker registration
  const getPoints = useCallback(() => initialPoints, [initialPoints]);
  const updatePoints = useCallback(
    (points: BezierPoint[]) => {
      // Set flag to prevent useEffect from running when we update internally
      isInternalUpdateRef.current = true;
      // Update the ref to track the last normalized points
      lastNormalizedPointsRef.current = points;
      // Update current points ref immediately for transformation callbacks
      currentPointsRef.current = points;
      setInitialPoints(points);
      // Clear flag immediately - it only needs to prevent the current useEffect run
      isInternalUpdateRef.current = false;
      // Track what we're sending to parent to detect circular updates
      lastSentToParentRef.current = points;
      onPointsChange?.(points);
    },
    [onPointsChange],
  );

  // Function to update current points ref - used by VectorTransformer during transformation
  const updateCurrentPointsRef = useCallback((points: BezierPoint[]) => {
    currentPointsRef.current = points;
  }, []);

  // Function to get current points ref - used by VectorTransformer during transformation
  const getCurrentPointsRef = useCallback(() => {
    return currentPointsRef.current;
  }, []);
  const setSelectedPointsStable = useCallback((selectedPoints: Set<number>) => {
    setSelectedPoints(selectedPoints);
  }, []);
  const setSelectedPointIndexStable = useCallback((index: number | null) => {
    setSelectedPointIndex(index);
  }, []);
  const getTransformStable = useCallback(() => transform, [transform]);
  const getFitScaleStable = useCallback(() => fitScale, [fitScale]);
  const getBoundsStable = useCallback(() => ({ width, height }), [width, height]);

  // Wrapper for onPointSelected to prevent infinite loops during programmatic selection
  const onPointSelectedWrapper = useCallback(
    (index: number | null) => {
      if (!isProgrammaticSelection.current && onPointSelected) {
        onPointSelected(index);
      }
    },
    [onPointSelected],
  );

  // Register instance with tracker
  useEffect(() => {
    const vectorInstance: VectorInstance = {
      id: instanceId,
      getPoints,
      updatePoints,
      setSelectedPoints: setSelectedPointsStable,
      setSelectedPointIndex: setSelectedPointIndexStable,
      onPointSelected: onPointSelectedWrapper,
      onTransformationComplete,
      getTransform: getTransformStable,
      getFitScale: getFitScaleStable,
      getBounds: getBoundsStable,
    };

    tracker.registerInstance(vectorInstance);

    return () => {
      tracker.unregisterInstance(instanceId);
    };
  }, [
    instanceId,
    tracker,
    getPoints,
    updatePoints,
    setSelectedPointsStable,
    setSelectedPointIndexStable,
    onPointSelectedWrapper,
    onTransformationComplete,
    getTransformStable,
    getFitScaleStable,
    getBoundsStable,
  ]);

  // Clear selection when component is disabled or not selected
  useEffect(() => {
    if (disabled || !selected) {
      setSelectedPointIndex(null);
      setSelectedPoints(new Set());
      setVisibleControlPoints(new Set());
      setDraggedControlPoint(null);
      setGhostPoint(null);
      setGhostPointDragInfo(null);
      setIsDraggingNewBezier(false);
      setNewPointDragIndex(null);
      // Hide all Bezier control points when not selected
      setVisibleControlPoints(new Set());
    }
  }, [selected]);
  const lastPos = useRef<{
    x: number;
    y: number;
    originalX?: number;
    originalY?: number;
    originalControlPoint1?: { x: number; y: number };
    originalControlPoint2?: { x: number; y: number };
  } | null>(null);

  // Set up Transformer nodes once when selection changes
  useEffect(() => {
    if (transformerRef.current) {
      if (effectiveSelectedPoints.size > SELECTION_SIZE.MULTI_SELECTION_MIN) {
        // Use setTimeout to ensure proxy nodes are rendered first
        setTimeout(() => {
          if (transformerRef.current) {
            // Set up proxy nodes once - transformer will manage them independently
            // Use getAllPoints() to get the correct proxy nodes for all points
            const allPoints = getAllPoints();
            const nodes = Array.from(effectiveSelectedPoints)
              .map((index) => {
                // Ensure the index is within bounds of all points
                if (index < allPoints.length) {
                  return proxyRefs.current[index];
                }
                return null;
              })
              .filter((node) => node?.getAbsoluteTransform) as Konva.Node[];

            if (nodes.length > 0) {
              // Always set the complete set of nodes - transformer will handle positioning
              transformerRef.current.nodes(nodes);
              transformerRef.current.getLayer()?.batchDraw();
            }
          }
        }, TRANSFORMER_SETUP_DELAY);
      } else {
        // Clear transformer when selection is less than minimum points for transformer
        setTimeout(() => {
          if (transformerRef.current) {
            transformerRef.current.nodes([]);
            transformerRef.current.getLayer()?.batchDraw();
          }
        }, TRANSFORMER_CLEAR_DELAY);
      }
    }
  }, [effectiveSelectedPoints]); // Depend on effectiveSelectedPoints to include transform mode

  // Note: We don't update proxy node positions during transformation
  // The transformer handles positioning the proxy nodes itself
  // This prevents conflicts and maintains the transformer's rotation state

  // Helper function to generate shape data and call transformation complete callback
  const notifyTransformationComplete = () => {
    // Get all points
    const allPoints = getAllPoints();

    if (format === ExportFormat.SIMPLE) {
      // Export in simple format
      // For simple format, we need to call a different callback or handle differently
      // since onTransformationComplete expects the complex format
    } else {
      // Export in regular format
      const exportedPoints = allPoints.map((point) => {
        const controlPoints: Array<{ x: number; y: number }> = [];

        if (point.isBezier) {
          if (point.controlPoint1) {
            controlPoints.push({
              x: point.controlPoint1.x,
              y: point.controlPoint1.y,
            });
          }
          if (point.controlPoint2) {
            controlPoints.push({
              x: point.controlPoint2.x,
              y: point.controlPoint2.y,
            });
          }
        }

        return {
          x: point.x,
          y: point.y,
          bezier: point.isBezier || false,
          controlPoints,
        };
      });

      // Check if we have enough points based on minPoints constraint
      const incomplete = minPoints !== undefined && allPoints.length < minPoints;

      const shapeData = {
        type: allowClose ? ShapeType.POLYGON : ShapeType.POLYLINE,
        isClosed: finalIsPathClosed,
        points: exportedPoints,
        incomplete,
      };

      onTransformationComplete?.(shapeData);
    }
  };

  // Helper function to check if we can add more points
  const canAddMorePoints = () => {
    return maxPoints === undefined || initialPoints.length < maxPoints;
  };

  // Update PointCreationManager props when relevant props change
  useEffect(() => {
    pointCreationManager.setProps({
      initialPoints,
      allowBezier,
      pixelSnapping,
      width,
      height,
      transform,
      fitScale,
      onPointsChange,
      onPointAdded,
      onPointEdited,
      canAddMorePoints,
      skeletonEnabled,
      lastAddedPointId,
      activePointId,
      setLastAddedPointId,
      setActivePointId,
      setVisibleControlPoints,
      setNewPointDragIndex,
      setIsDraggingNewBezier,
      ghostPoint,
      selectedPoints: effectiveSelectedPoints,
      isShiftKeyHeld,
      setGhostPoint,
    });
  }, [
    pointCreationManager,
    initialPoints,
    effectiveSelectedPoints,
    allowBezier,
    pixelSnapping,
    width,
    height,
    transform,
    fitScale,
    onPointsChange,
    onPointAdded,
    onPointEdited,
    canAddMorePoints,
    skeletonEnabled,
    lastAddedPointId,
    activePointId,
    setLastAddedPointId,
    setActivePointId,
    setVisibleControlPoints,
    setNewPointDragIndex,
    setIsDraggingNewBezier,
    ghostPoint,
    isShiftKeyHeld,
    setGhostPoint,
  ]);

  // Helper function to get all points for rendering and interactions
  const getAllPoints = () => {
    return [...initialPoints];
  };

  // Helper function to get all line segments for rendering
  const getAllLineSegments = () => {
    const segments: Array<{ from: BezierPoint; to: BezierPoint }> = [];
    const allPoints = getAllPoints();

    // In skeleton mode, we need to handle segments differently
    if (skeletonEnabled) {
      // Create a map for quick point lookup
      const pointMap = new Map<string, BezierPoint>();
      for (const point of allPoints) {
        pointMap.set(point.id, point);
      }

      // First, add all segments that have explicit prevPointId relationships
      for (const point of allPoints) {
        if (point.prevPointId) {
          const prevPoint = pointMap.get(point.prevPointId);
          if (prevPoint) {
            segments.push({ from: prevPoint, to: point });
          }
        }
      }

      // Then, handle points that don't have prevPointId but should be connected
      // These are points that were added but not yet connected to the active point
      const connectedPointIds = new Set<string>();
      segments.forEach((segment) => {
        connectedPointIds.add(segment.from.id);
        connectedPointIds.add(segment.to.id);
      });

      // Find points that aren't connected yet
      const unconnectedPoints = allPoints.filter((point) => !connectedPointIds.has(point.id));

      if (unconnectedPoints.length > 0 && activePointId) {
        const activePoint = pointMap.get(activePointId);
        if (activePoint) {
          // Connect unconnected points to the active point
          for (const unconnectedPoint of unconnectedPoints) {
            segments.push({ from: activePoint, to: unconnectedPoint });
          }
        }
      }
    } else {
      // Non-skeleton mode: use the original prevPointId logic
      // Create a map for quick point lookup
      const pointMap = new Map<string, BezierPoint>();
      for (const point of allPoints) {
        pointMap.set(point.id, point);
      }

      // Find all id-prevPointId pairs and create line segments
      for (const point of allPoints) {
        if (point.prevPointId) {
          const prevPoint = pointMap.get(point.prevPointId);
          if (prevPoint) {
            segments.push({ from: prevPoint, to: point });
          }
        }
      }
    }

    // The path closure is now determined by point references
    // If the first point has a prevPointId that references the last point,
    // the closing segment will be created automatically through the prevPointId logic above
    // No need to add an additional closing segment

    return segments;
  };

  // Helper function to get point info
  const getPointInfo = (globalIndex: number) => {
    if (globalIndex < initialPoints.length) {
      return {
        pathType: PathType.MAIN,
        pathIndex: globalIndex,
        point: initialPoints[globalIndex],
      };
    }
    return null;
  };

  // Helper function to update a point by its global index
  const updatePointByGlobalIndex = (globalIndex: number, updatedPoint: BezierPoint) => {
    // Update main path point
    if (globalIndex >= 0 && globalIndex < initialPoints.length) {
      const newPoints = [...initialPoints];
      newPoints[globalIndex] = updatedPoint;
      setInitialPoints(newPoints); // Update internal state
      onPointsChange?.(newPoints);
      return;
    }
  };

  // Convert a point between regular and Bezier
  const convertPointHandler = (pointIndex: number) => {
    const pointInfo = getPointInfo(pointIndex);
    if (!pointInfo) {
      return;
    }

    const point = pointInfo.point;

    if (point.isBezier) {
      // Convert from Bezier to regular
      convertPoint(
        pointInfo.pathIndex,
        initialPoints,
        onPointConverted,
        onPointsChange,
        onPathShapeChanged,
        setVisibleControlPoints,
        visibleControlPoints,
      );

      // Hide control points for the converted point
      setVisibleControlPoints((prev) => {
        const newSet = new Set(prev);
        newSet.delete(pointIndex);
        return newSet;
      });
    } else {
      // Check if bezier is allowed
      if (!allowBezier) {
        return;
      }

      // Convert from regular to Bezier
      // Don't convert first or last points of main path
      if (pointInfo.pathIndex === 0 || pointInfo.pathIndex === initialPoints.length - 1) {
        return;
      }

      convertPoint(
        pointInfo.pathIndex,
        initialPoints,
        onPointConverted,
        onPointsChange,
        onPathShapeChanged,
        setVisibleControlPoints,
        visibleControlPoints,
      );

      // Make control points visible for the converted point
      setVisibleControlPoints((prev) => {
        const newSet = new Set(prev);
        newSet.add(pointIndex);
        return newSet;
      });
    }

    // Notify transformation complete after point conversion
    notifyTransformationComplete();
  };

  // Expose methods through ref
  useImperativeHandle(ref, () => ({
    convertPoint: convertPointHandler,
    close: () => {
      if (!allowClose || initialPoints.length < MIN_POINTS_FOR_CLOSING) {
        return false;
      }

      // Check if we can close the path based on point count or bezier points
      const canClosePath = () => {
        // Allow closing if we have more than 2 points
        if (initialPoints.length > MIN_POINTS_FOR_BEZIER_CLOSING) {
          return true;
        }

        // Allow closing if we have at least one bezier point
        const hasBezierPoint = initialPoints.some((point) => point.isBezier);
        if (hasBezierPoint) {
          return true;
        }

        return false;
      };

      if (!canClosePath()) {
        return false;
      }

      // Additional validation: ensure we meet the minimum points requirement
      if (minPoints && initialPoints.length < minPoints) {
        return false;
      }

      // Check if path is already closed
      if (finalIsPathClosed) {
        return true;
      }

      // Close the path by setting the first point's prevPointId to the last point's ID
      const firstPoint = initialPoints[0];
      const lastPoint = initialPoints[initialPoints.length - 1];

      const updatedPoints = [...initialPoints];
      updatedPoints[0] = {
        ...firstPoint,
        prevPointId: lastPoint.id,
      };

      // Update the points and notify parent
      onPointsChange?.(updatedPoints);

      // Update the internal path closed state and notify parent
      setIsPathClosed(true);

      return true;
    },
    selectPointsByIds: (pointIds: string[]) => {
      // Check if this instance can have selection
      if (!tracker.canInstanceHaveSelection(instanceId)) {
        return; // Block the selection
      }

      // Find the indices of the points with the given IDs
      const selectedIndices = new Set<number>();
      let primarySelectedIndex: number | null = null;

      for (let i = 0; i < initialPoints.length; i++) {
        if (pointIds.includes(initialPoints[i].id)) {
          selectedIndices.add(i);
          // Set the first found point as the primary selected point
          if (primarySelectedIndex === null) {
            primarySelectedIndex = i;
          }
        }
      }

      // Use tracker for global selection management
      isProgrammaticSelection.current = true;
      tracker.selectPoints(instanceId, selectedIndices);
      setTimeout(() => {
        isProgrammaticSelection.current = false;
      }, 0);
    },
    clearSelection: () => {
      // Use tracker for global selection management
      isProgrammaticSelection.current = true;
      tracker.selectPoints(instanceId, new Set());
      setTimeout(() => {
        isProgrammaticSelection.current = false;
      }, 0);
    },
    getSelectedPointIds: () => {
      const selectedIds: string[] = [];
      selectedPoints.forEach((index) => {
        if (index < initialPoints.length) {
          selectedIds.push(initialPoints[index].id);
        }
      });
      return selectedIds;
    },
    exportShape: () => {
      const exportedPoints = initialPoints.map((point) => {
        const controlPoints: Array<{ x: number; y: number }> = [];

        if (point.isBezier) {
          if (point.controlPoint1) {
            controlPoints.push({
              x: point.controlPoint1.x,
              y: point.controlPoint1.y,
            });
          }
          if (point.controlPoint2) {
            controlPoints.push({
              x: point.controlPoint2.x,
              y: point.controlPoint2.y,
            });
          }
        }

        return {
          x: point.x,
          y: point.y,
          bezier: point.isBezier || false,
          controlPoints,
        };
      });

      // Check if we have enough points based on minPoints constraint
      const incomplete = minPoints !== undefined && initialPoints.length < minPoints;

      return {
        type: allowClose ? ShapeType.POLYGON : ShapeType.POLYLINE,
        isClosed: finalIsPathClosed,
        points: exportedPoints,
        incomplete,
      };
    },
    exportSimpleShape: () => {
      const simplePoints = convertBezierToSimplePoints(initialPoints);

      // Check if we have enough points based on minPoints constraint
      const incomplete = minPoints !== undefined && initialPoints.length < minPoints;

      return {
        type: allowClose ? ShapeType.POLYGON : ShapeType.POLYLINE,
        isClosed: finalIsPathClosed,
        points: simplePoints,
        incomplete,
      };
    },
    // Programmatic point creation methods
    startPoint: (x: number, y: number) => pointCreationManager.startPoint(x, y),
    updatePoint: (x: number, y: number) => pointCreationManager.updatePoint(x, y),
    commitPoint: (x: number, y: number) => pointCreationManager.commitPoint(x, y),
    // Programmatic point transformation methods
    translatePoints: (dx: number, dy: number, pointIds?: string[]) => {
      const pointsToTransform = pointIds ? initialPoints.filter((p) => pointIds.includes(p.id)) : initialPoints;

      const updatedPoints = initialPoints.map((point) => {
        if (pointsToTransform.some((p) => p.id === point.id)) {
          const updatedPoint = { ...point };

          // Apply translation to main point
          updatedPoint.x += dx;
          updatedPoint.y += dy;

          // Apply translation to control points if it's a bezier point
          if (updatedPoint.isBezier) {
            if (updatedPoint.controlPoint1) {
              updatedPoint.controlPoint1 = {
                ...updatedPoint.controlPoint1,
                x: updatedPoint.controlPoint1.x + dx,
                y: updatedPoint.controlPoint1.y + dy,
              };
            }
            if (updatedPoint.controlPoint2) {
              updatedPoint.controlPoint2 = {
                ...updatedPoint.controlPoint2,
                x: updatedPoint.controlPoint2.x + dx,
                y: updatedPoint.controlPoint2.y + dy,
              };
            }
          }

          return updatedPoint;
        }
        return point;
      });

      onPointsChange?.(updatedPoints);
    },
    rotatePoints: (angle: number, centerX: number, centerY: number, pointIds?: string[]) => {
      const pointsToTransform = pointIds ? initialPoints.filter((p) => pointIds.includes(p.id)) : initialPoints;

      // If no point IDs provided, calculate center of the entire shape
      let actualCenterX = centerX;
      let actualCenterY = centerY;

      if (!pointIds) {
        // Use the accurate shape bounding box calculation
        const bbox = calculateShapeBoundingBox(initialPoints);
        actualCenterX = (bbox.left + bbox.right) / CENTER_CALCULATION_DIVISOR;
        actualCenterY = (bbox.top + bbox.bottom) / CENTER_CALCULATION_DIVISOR;
      }

      const radians = angle * DEGREES_TO_RADIANS;
      const cos = Math.cos(radians);
      const sin = Math.sin(radians);

      const updatedPoints = initialPoints.map((point) => {
        if (pointsToTransform.some((p) => p.id === point.id)) {
          const updatedPoint = { ...point };

          // Apply rotation to main point
          const dx = updatedPoint.x - actualCenterX;
          const dy = updatedPoint.y - actualCenterY;
          updatedPoint.x = actualCenterX + dx * cos - dy * sin;
          updatedPoint.y = actualCenterY + dx * sin + dy * cos;

          // Apply rotation to control points if it's a bezier point
          if (updatedPoint.isBezier) {
            if (updatedPoint.controlPoint1) {
              const cp1Dx = updatedPoint.controlPoint1.x - actualCenterX;
              const cp1Dy = updatedPoint.controlPoint1.y - actualCenterY;
              updatedPoint.controlPoint1 = {
                ...updatedPoint.controlPoint1,
                x: actualCenterX + cp1Dx * cos - cp1Dy * sin,
                y: actualCenterY + cp1Dx * sin + cp1Dy * cos,
              };
            }
            if (updatedPoint.controlPoint2) {
              const cp2Dx = updatedPoint.controlPoint2.x - actualCenterX;
              const cp2Dy = updatedPoint.controlPoint2.y - actualCenterY;
              updatedPoint.controlPoint2 = {
                ...updatedPoint.controlPoint2,
                x: actualCenterX + cp2Dx * cos - cp2Dy * sin,
                y: actualCenterY + cp2Dx * sin + cp2Dy * cos,
              };
            }
          }

          return updatedPoint;
        }
        return point;
      });

      onPointsChange?.(updatedPoints);
    },
    scalePoints: (scaleX: number, scaleY: number, centerX: number, centerY: number, pointIds?: string[]) => {
      const pointsToTransform = pointIds ? initialPoints.filter((p) => pointIds.includes(p.id)) : initialPoints;

      // If no point IDs provided, calculate center of the entire shape
      let actualCenterX = centerX;
      let actualCenterY = centerY;

      if (!pointIds) {
        // Use the accurate shape bounding box calculation
        const bbox = calculateShapeBoundingBox(initialPoints);
        actualCenterX = (bbox.left + bbox.right) / CENTER_CALCULATION_DIVISOR;
        actualCenterY = (bbox.top + bbox.bottom) / CENTER_CALCULATION_DIVISOR;
      }

      const updatedPoints = initialPoints.map((point) => {
        if (pointsToTransform.some((p) => p.id === point.id)) {
          const updatedPoint = { ...point };

          // Apply scaling to main point
          const dx = updatedPoint.x - actualCenterX;
          const dy = updatedPoint.y - actualCenterY;
          updatedPoint.x = actualCenterX + dx * scaleX;
          updatedPoint.y = actualCenterY + dy * scaleY;

          // Apply scaling to control points if it's a bezier point
          if (updatedPoint.isBezier) {
            if (updatedPoint.controlPoint1) {
              const cp1Dx = updatedPoint.controlPoint1.x - actualCenterX;
              const cp1Dy = updatedPoint.controlPoint1.y - actualCenterY;
              updatedPoint.controlPoint1 = {
                ...updatedPoint.controlPoint1,
                x: actualCenterX + cp1Dx * scaleX,
                y: actualCenterY + cp1Dy * scaleY,
              };
            }
            if (updatedPoint.controlPoint2) {
              const cp2Dx = updatedPoint.controlPoint2.x - actualCenterX;
              const cp2Dy = updatedPoint.controlPoint2.y - actualCenterY;
              updatedPoint.controlPoint2 = {
                ...updatedPoint.controlPoint2,
                x: actualCenterX + cp2Dx * scaleX,
                y: actualCenterY + cp2Dy * scaleY,
              };
            }
          }

          return updatedPoint;
        }
        return point;
      });

      onPointsChange?.(updatedPoints);
    },
    transformPoints: (
      transformation: {
        dx?: number;
        dy?: number;
        rotation?: number;
        scaleX?: number;
        scaleY?: number;
        centerX?: number;
        centerY?: number;
      },
      pointIds?: string[],
    ) => {
      const pointsToTransform = pointIds ? initialPoints.filter((p) => pointIds.includes(p.id)) : initialPoints;

      // If no point IDs provided and we need center point, calculate center of the entire shape
      let actualCenterX = transformation.centerX;
      let actualCenterY = transformation.centerY;

      if (
        !pointIds &&
        (transformation.rotation !== undefined ||
          transformation.scaleX !== undefined ||
          transformation.scaleY !== undefined)
      ) {
        // Use the accurate shape bounding box calculation
        const bbox = calculateShapeBoundingBox(initialPoints);
        actualCenterX = (bbox.left + bbox.right) / CENTER_CALCULATION_DIVISOR;
        actualCenterY = (bbox.top + bbox.bottom) / CENTER_CALCULATION_DIVISOR;
      }

      const updatedPoints = initialPoints.map((point) => {
        if (pointsToTransform.some((p) => p.id === point.id)) {
          const updatedPoint = { ...point };

          // Apply translation
          if (transformation.dx !== undefined) {
            updatedPoint.x += transformation.dx;
            updatedPoint.y += transformation.dy || DEFAULT_OFFSET;
          }

          // Apply rotation and scaling (need center point)
          if (
            (transformation.rotation !== undefined ||
              transformation.scaleX !== undefined ||
              transformation.scaleY !== undefined) &&
            actualCenterX !== undefined &&
            actualCenterY !== undefined
          ) {
            let finalX = updatedPoint.x;
            let finalY = updatedPoint.y;

            // Apply scaling
            if (transformation.scaleX !== undefined || transformation.scaleY !== undefined) {
              const scaleX = transformation.scaleX || DEFAULT_SCALE;
              const scaleY = transformation.scaleY || DEFAULT_SCALE;
              const dx = finalX - actualCenterX;
              const dy = finalY - actualCenterY;
              finalX = actualCenterX + dx * scaleX;
              finalY = actualCenterY + dy * scaleY;
            }

            // Apply rotation
            if (transformation.rotation !== undefined) {
              const radians = transformation.rotation * DEGREES_TO_RADIANS;
              const cos = Math.cos(radians);
              const sin = Math.sin(radians);
              const dx = finalX - actualCenterX;
              const dy = finalY - actualCenterY;
              finalX = actualCenterX + dx * cos - dy * sin;
              finalY = actualCenterY + dx * sin + dy * cos;
            }

            updatedPoint.x = finalX;
            updatedPoint.y = finalY;
          }

          // Apply transformations to control points if it's a bezier point
          if (updatedPoint.isBezier) {
            if (updatedPoint.controlPoint1) {
              const cp1 = { ...updatedPoint.controlPoint1 };

              // Apply translation
              if (transformation.dx !== undefined) {
                cp1.x += transformation.dx;
                cp1.y += transformation.dy || 0;
              }

              // Apply rotation and scaling
              if (
                (transformation.rotation !== undefined ||
                  transformation.scaleX !== undefined ||
                  transformation.scaleY !== undefined) &&
                actualCenterX !== undefined &&
                actualCenterY !== undefined
              ) {
                let finalCp1X = cp1.x;
                let finalCp1Y = cp1.y;

                // Apply scaling
                if (transformation.scaleX !== undefined || transformation.scaleY !== undefined) {
                  const scaleX = transformation.scaleX || 1;
                  const scaleY = transformation.scaleY || 1;
                  const dx = finalCp1X - actualCenterX;
                  const dy = finalCp1Y - actualCenterY;
                  finalCp1X = actualCenterX + dx * scaleX;
                  finalCp1Y = actualCenterY + dy * scaleY;
                }

                // Apply rotation
                if (transformation.rotation !== undefined) {
                  const radians = transformation.rotation * DEGREES_TO_RADIANS;
                  const cos = Math.cos(radians);
                  const sin = Math.sin(radians);
                  const dx = finalCp1X - actualCenterX;
                  const dy = finalCp1Y - actualCenterY;
                  finalCp1X = actualCenterX + dx * cos - dy * sin;
                  finalCp1Y = actualCenterY + dx * sin + dy * cos;
                }

                cp1.x = finalCp1X;
                cp1.y = finalCp1Y;
              }

              updatedPoint.controlPoint1 = cp1;
            }

            if (updatedPoint.controlPoint2) {
              const cp2 = { ...updatedPoint.controlPoint2 };

              // Apply translation
              if (transformation.dx !== undefined) {
                cp2.x += transformation.dx;
                cp2.y += transformation.dy || 0;
              }

              // Apply rotation and scaling
              if (
                (transformation.rotation !== undefined ||
                  transformation.scaleX !== undefined ||
                  transformation.scaleY !== undefined) &&
                actualCenterX !== undefined &&
                actualCenterY !== undefined
              ) {
                let finalCp2X = cp2.x;
                let finalCp2Y = cp2.y;

                // Apply scaling
                if (transformation.scaleX !== undefined || transformation.scaleY !== undefined) {
                  const scaleX = transformation.scaleX || 1;
                  const scaleY = transformation.scaleY || 1;
                  const dx = finalCp2X - actualCenterX;
                  const dy = finalCp2Y - actualCenterY;
                  finalCp2X = actualCenterX + dx * scaleX;
                  finalCp2Y = actualCenterY + dy * scaleY;
                }

                // Apply rotation
                if (transformation.rotation !== undefined) {
                  const radians = transformation.rotation * DEGREES_TO_RADIANS;
                  const cos = Math.cos(radians);
                  const sin = Math.sin(radians);
                  const dx = finalCp2X - actualCenterX;
                  const dy = finalCp2Y - actualCenterY;
                  finalCp2X = actualCenterX + dx * cos - dy * sin;
                  finalCp2Y = actualCenterY + dx * sin + dy * cos;
                }

                cp2.x = finalCp2X;
                cp2.y = finalCp2Y;
              }

              updatedPoint.controlPoint2 = cp2;
            }
          }

          return updatedPoint;
        }
        return point;
      });

      onPointsChange?.(updatedPoints);
    },
    // Shape analysis methods
    getShapeBoundingBox: () => {
      return calculateShapeBoundingBox(initialPoints);
    },
    // Hit testing method
    isPointOverShape: (x: number, y: number, hitRadius = 10) => {
      // Convert screen coordinates to image coordinates
      const imageX = (x - transform.offsetX) / (fitScale * transform.zoom);
      const imageY = (y - transform.offsetY) / (fitScale * transform.zoom);
      const point = { x: imageX, y: imageY };

      // If no points, return false
      if (initialPoints.length === 0) {
        return false;
      }

      // First check if hovering over any individual point (vertices)
      for (const vertex of initialPoints) {
        const distance = getDistance(point, vertex);
        if (distance <= hitRadius / (fitScale * transform.zoom)) {
          return true; // Hovering over a vertex
        }
      }

      // For single point, we already checked above, so return false if not hit
      if (initialPoints.length === 1) {
        return false;
      }

      // For polylines and polygons, check if point is close to any segment
      const closestPathPoint = findClosestPointOnPath(point, initialPoints, allowClose, finalIsPathClosed);

      if (closestPathPoint) {
        const distance = getDistance(point, closestPathPoint.point);
        return distance <= hitRadius / (fitScale * transform.zoom);
      }

      // For closed polygons, also check if point is inside the polygon
      if (finalIsPathClosed && initialPoints.length >= 3) {
        return isPointInPolygon(point, initialPoints);
      }

      return false;
    },
    // Multi-region transformation method - applies group transform to points
    commitMultiRegionTransform,
    // Delete multiple points by their IDs
    deletePointsByIds: (pointIds: string[]) => {
      if (!pointIds || pointIds.length === 0) return;

      // Find indices of points to delete (in reverse order to maintain correct indices)
      const indicesToDelete = pointIds
        .map((id) => initialPoints.findIndex((p) => p.id === id))
        .filter((idx) => idx >= 0)
        .sort((a, b) => b - a); // Sort descending to delete from end to start

      if (indicesToDelete.length === 0) return;

      // Create a set of deleted point IDs for quick lookup
      const deletedPointIds = new Set(pointIds);

      // Delete points one by one in reverse order (from highest index to lowest)
      // This ensures indices remain valid as we delete
      let updatedPoints = [...initialPoints];
      let updatedSelectedPointIndex = selectedPointIndex;
      let updatedLastAddedPointId = lastAddedPointId;

      for (const index of indicesToDelete) {
        if (index < 0 || index >= updatedPoints.length) continue;

        const deletedPoint = updatedPoints[index];
        const newPoints = [...updatedPoints];
        newPoints.splice(index, 1);

        // Reconnect points after deletion (same logic as deletePoint function)
        // For skeleton mode: use deleted point's prevPointId
        for (let i = 0; i < newPoints.length; i++) {
          const point = newPoints[i];
          if (point.prevPointId === deletedPoint.id) {
            let newPrevPointId: string | undefined = deletedPoint.prevPointId;

            // Edge cases:
            if (index === 0) {
              // If we deleted the first point, this point becomes the new first point
              newPrevPointId = undefined;
            } else if (!deletedPoint.prevPointId) {
              // If deleted point had no prevPointId (was a root point), this point becomes a root
              newPrevPointId = undefined;
            } else if (deletedPointIds.has(deletedPoint.prevPointId)) {
              // If the previous point is also being deleted, we need to find the next valid ancestor
              // This handles cascading deletions in skeleton mode
              let ancestorId = deletedPoint.prevPointId;
              while (ancestorId && deletedPointIds.has(ancestorId)) {
                const ancestorIndex = updatedPoints.findIndex((p) => p.id === ancestorId);
                if (ancestorIndex >= 0 && ancestorIndex < updatedPoints.length) {
                  ancestorId = updatedPoints[ancestorIndex].prevPointId;
                } else {
                  ancestorId = undefined;
                  break;
                }
              }
              newPrevPointId = ancestorId;
            }
            // Otherwise, use deletedPoint.prevPointId which is correct for both linear and skeleton modes

            newPoints[i] = {
              ...point,
              prevPointId: newPrevPointId,
            };
          }
        }

        // Update selection state
        if (updatedSelectedPointIndex === index) {
          updatedSelectedPointIndex = null;
          onPointSelected?.(null);
        } else if (updatedSelectedPointIndex !== null && updatedSelectedPointIndex > index) {
          updatedSelectedPointIndex = updatedSelectedPointIndex - 1;
        }

        // Handle active point
        if (updatedLastAddedPointId === deletedPoint.id) {
          if (newPoints.length > 0) {
            const newLastPoint = newPoints[newPoints.length - 1];
            updatedLastAddedPointId = newLastPoint.id;
          } else {
            updatedLastAddedPointId = null;
          }
        }

        // Update visible control points
        setVisibleControlPoints((prev) => {
          const newSet = new Set(prev);
          newSet.delete(index);
          const adjustedSet = new Set<number>();
          for (const pointIndex of Array.from(newSet)) {
            if (pointIndex > index) {
              adjustedSet.add(pointIndex - 1);
            } else {
              adjustedSet.add(pointIndex);
            }
          }
          return adjustedSet;
        });

        onPointRemoved?.(deletedPoint, index);
        updatedPoints = newPoints;
      }

      // Clear selection after deleting all selected points
      setSelectedPointIndex(updatedSelectedPointIndex);
      setLastAddedPointId(updatedLastAddedPointId);
      tracker.selectPoints(instanceId, new Set());
      onPointsChange?.(updatedPoints);
    },
  }));

  // Ensure commitMultiRegionTransform is called when ImageTransformer's onDragEnd fires
  // ImageTransformer will call applyTransform which calls commitMultiRegionTransform
  // But we also need to ensure the Group position is preserved when selection is cleared
  // by committing the transform before the selection is cleared
  useEffect(() => {
    if (!isMultiRegionSelected && transformableGroupRef.current) {
      // When selection is cleared, commit any pending transform first
      const group = transformableGroupRef.current;
      const currentX = group.x();
      const currentY = group.y();

      // If there's a pending transform, commit it before the Group is unmounted/reset
      if ((currentX !== 0 || currentY !== 0) && initialTransformRef.current) {
        commitMultiRegionTransform();
        handleTransformEnd();
      }
    }
  }, [isMultiRegionSelected, commitMultiRegionTransform, handleTransformEnd]);

  // Clean up click timeout on unmount
  useEffect(() => {
    return () => {
      if (clickTimeoutRef.current) {
        clearTimeout(clickTimeoutRef.current);
        clickTimeoutRef.current = null;
      }
    };
  }, []);

  // Set up stage-level event listeners for cursor position, ghost point, and point dragging
  // This allows these features to work even when the invisible shape is disabled
  useEffect(() => {
    // Prevent running if handlers are already attached
    // This stops the infinite loop caused by state updates triggering re-renders
    if (handlersAttachedRef.current) {
      return () => {}; // Return empty cleanup function
    }

    const group = stageRef.current;
    if (!group) {
      // If stageRef is not available yet, try again after a short delay
      const retryTimeout = setTimeout(() => {
        if (!handlersAttachedRef.current) {
          // Force re-run by incrementing retry counter
          setStageReadyRetry((prev) => prev + 1);
        }
      }, 100);
      return () => {
        clearTimeout(retryTimeout);
      };
    }

    const stage = group.getStage();
    if (!stage) {
      // If stage is not available yet, try again after a short delay
      const retryTimeout = setTimeout(() => {
        if (!handlersAttachedRef.current) {
          // Force re-run by incrementing retry counter
          setStageReadyRetry((prev) => prev + 1);
        }
      }, 100);
      return () => {
        clearTimeout(retryTimeout);
      };
    }

    handlersAttachedRef.current = true;

    // Helper function to calculate and set ghost point based on current cursor position
    const calculateGhostPoint = (shiftKeyState?: boolean, eventPos?: { x: number; y: number }) => {
      const group = stageRef.current;
      if (!group) return;
      const stage = group.getStage();
      if (!stage) return;

      // Use event position if provided, otherwise fall back to stage.getPointerPosition()
      const pos = eventPos || stage.getPointerPosition();
      if (!pos) return;

      const {
        transform,
        fitScale,
        x,
        y,
        width,
        height,
        initialPoints,
        allowClose,
        finalIsPathClosed,
        pixelSnapping,
        isDraggingNewBezier,
        ghostPointDragInfo,
        selected,
        disabled,
        isShiftKeyHeld: refShiftState,
      } = currentValuesRef.current;

      if (disabled || !selected || isDragging.current || isDraggingNewBezier || ghostPointDragInfo?.isDragging) {
        return;
      }

      const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);

      // Only process ghost point logic if within bounds
      if (imagePos.x >= 0 && imagePos.x <= width && imagePos.y >= 0 && imagePos.y <= height) {
        // Use provided shiftKeyState or fall back to ref value
        const currentShiftState = shiftKeyState !== undefined ? shiftKeyState : (refShiftState ?? false);

        if (initialPoints.length >= 2 && currentShiftState) {
          const scale = transform.zoom * fitScale;
          const hitRadius = HIT_RADIUS.SELECTION / scale;
          let isOverPoint = false;

          for (let i = 0; i < initialPoints.length; i++) {
            const point = initialPoints[i];
            const distance = Math.sqrt((imagePos.x - point.x) ** 2 + (imagePos.y - point.y) ** 2);

            if (distance <= hitRadius) {
              isOverPoint = true;
              break;
            }
          }

          if (isOverPoint) {
            setGhostPoint(null);
          } else {
            const closestPathPoint = findClosestPointOnPath(imagePos, initialPoints, allowClose, finalIsPathClosed);

            if (closestPathPoint) {
              const snappedGhostPoint = snapToPixel(closestPathPoint.point, pixelSnapping);

              // Always create a new ghost point object to ensure React detects the change
              let newGhostPoint: { x: number; y: number; prevPointId: string; nextPointId: string };

              if (closestPathPoint.segmentIndex === initialPoints.length) {
                const lastPoint = initialPoints[initialPoints.length - 1];
                const firstPoint = initialPoints[0];

                newGhostPoint = {
                  x: snappedGhostPoint.x,
                  y: snappedGhostPoint.y,
                  prevPointId: lastPoint.id,
                  nextPointId: firstPoint.id,
                };
              } else {
                const currentPoint = initialPoints[closestPathPoint.segmentIndex];
                const prevPoint = currentPoint?.prevPointId
                  ? initialPoints.find((p) => p.id === currentPoint.prevPointId)
                  : null;

                if (currentPoint && prevPoint) {
                  newGhostPoint = {
                    x: snappedGhostPoint.x,
                    y: snappedGhostPoint.y,
                    prevPointId: prevPoint.id,
                    nextPointId: currentPoint.id,
                  };
                } else {
                  setGhostPoint(null);
                  return;
                }
              }

              // Always update the ghost point with a new object reference
              // This ensures React detects the change and re-renders
              setGhostPoint({ ...newGhostPoint });

              // Also update directly via ref for immediate visual update
              if (ghostPointRef.current) {
                ghostPointRef.current.updatePosition(newGhostPoint.x, newGhostPoint.y);
              }
            } else {
              setGhostPoint(null);
            }
          }
        } else {
          setGhostPoint(null);
        }
      } else {
        setGhostPoint(null);
      }
    };

    // Store the function in a ref so it can be accessed from handleKeyDown
    calculateGhostPointRef.current = calculateGhostPoint;

    // Only set up stage-level dragging when disableInternalPointAddition is true
    // Otherwise, let the layer handlers handle it
    if (!disableInternalPointAddition) {
      // Still handle cursor position and ghost point
      const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
        // Use stage.getPointerPosition() directly for consistent coordinate space
        const pos = stage.getPointerPosition();
        if (!pos) return;

        // Get current values from ref to avoid stale closures
        const {
          transform,
          fitScale,
          x,
          y,
          width,
          height,
          initialPoints,
          allowClose,
          finalIsPathClosed,
          pixelSnapping,
          isDraggingNewBezier,
          ghostPointDragInfo,
          selected,
        } = currentValuesRef.current;

        // Update Shift key state from the event to keep it in sync
        if (e.evt.shiftKey !== isShiftKeyHeld) {
          setIsShiftKeyHeld(e.evt.shiftKey);
        }

        // Debug: Log coordinate conversion
        const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);

        // Always update cursor position (even outside bounds) so ghost line can work
        cursorPositionRef.current = imagePos;

        // Use RAF to batch redraw calls for performance
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        ghostLineRafRef.current = requestAnimationFrame(() => {
          stage.batchDraw();
        });

        // Recalculate ghost point using the helper function
        // Pass the event's shiftKey state and position for real-time updates
        calculateGhostPoint(e.evt.shiftKey, pos);
      };

      const handleStageMouseEnter = (e: Konva.KonvaEventObject<MouseEvent>) => {
        // Capture cursor position when mouse enters stage so ghost line can render immediately
        const pos = e.target.getStage()?.getPointerPosition();
        if (pos) {
          const { transform, fitScale, x, y } = currentValuesRef.current;
          const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
          cursorPositionRef.current = imagePos;
          // Trigger a redraw to show ghost line
          if (ghostLineRafRef.current) {
            cancelAnimationFrame(ghostLineRafRef.current);
          }
          ghostLineRafRef.current = requestAnimationFrame(() => {
            stage.batchDraw();
          });
        }
      };

      const handleStageMouseLeave = () => {
        cursorPositionRef.current = null;
        setGhostPoint(null);
      };

      stage.on("mousemove", handleStageMouseMove);
      stage.on("mouseenter", handleStageMouseEnter);
      stage.on("mouseleave", handleStageMouseLeave);

      // Try to initialize cursor position if mouse is already over the stage
      // This ensures ghost line can render immediately even if mouseenter didn't fire
      const tryInitializeCursorPosition = () => {
        const pos = stage.getPointerPosition();
        if (pos) {
          const { transform, fitScale, x, y } = currentValuesRef.current;
          const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
          cursorPositionRef.current = imagePos;
          // Trigger a redraw to show ghost line
          if (ghostLineRafRef.current) {
            cancelAnimationFrame(ghostLineRafRef.current);
          }
          ghostLineRafRef.current = requestAnimationFrame(() => {
            stage.batchDraw();
          });
        }
      };

      // Try to initialize immediately
      tryInitializeCursorPosition();

      // Also try after a short delay in case the stage isn't ready yet
      const initTimeout = setTimeout(() => {
        tryInitializeCursorPosition();
      }, 0);

      return () => {
        clearTimeout(initTimeout);
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        stage.off("mousemove", handleStageMouseMove);
        stage.off("mouseenter", handleStageMouseEnter);
        stage.off("mouseleave", handleStageMouseLeave);
      };
    }

    // When disableInternalPointAddition is true, handle point dragging at stage level
    const handleStageMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
      // Get current values from ref to avoid stale closures
      const {
        initialPoints,
        effectiveSelectedPoints,
        instanceId,
        transform,
        fitScale,
        x,
        y,
        skeletonEnabled,
        activePointId,
        lastAddedPointId,
        allowClose,
        finalIsPathClosed,
        selected,
        disabled,
        onFinish,
      } = currentValuesRef.current;

      // Prevent all interactions when disabled
      if (disabled) {
        return;
      }

      // Check if event target belongs to this instance's group
      const target = e.target;
      let targetGroup: Konva.Node | null = target;
      while (targetGroup && targetGroup !== group) {
        targetGroup = targetGroup.getParent();
      }
      // If target is not within our group, ignore this event
      if (targetGroup !== group) return;

      // Use e.target.getStage() to match how layer handlers get pointer position
      const pos = e.target.getStage()?.getPointerPosition();
      if (!pos) return;

      const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);

      // Check if clicking on a point (by checking if target is a Circle with name starting with "point-")
      const targetName = target.name();
      if (targetName && targetName.startsWith("point-")) {
        // Extract point index from name (format: "point-{index}")
        const match = targetName.match(/^point-(\d+)$/);
        if (match) {
          const pointIndex = Number.parseInt(match[1], 10);
          if (pointIndex >= 0 && pointIndex < initialPoints.length) {
            const point = initialPoints[pointIndex];

            // If cmd-click, don't handle it here - let the onClick handler on the Circle component handle it
            // The onClick handler has the correct pointIndex, while this handler would need to find it by distance
            // which could select the wrong point when multiple points are close together
            if (e.evt.ctrlKey || e.evt.metaKey) {
              // Just prevent event propagation and return - let onClick handle the selection
              e.evt.stopPropagation();
              return;
            }

            // Normal click - prevent event propagation to avoid region deselection
            e.evt.stopPropagation();

            // Store the potential drag target but don't start dragging yet
            // We'll start dragging only if the mouse moves beyond a threshold
            setDraggedPointIndex(pointIndex);
            lastPos.current = {
              x: e.evt.clientX,
              y: e.evt.clientY,
              originalX: point.x,
              originalY: point.y,
              originalControlPoint1: point.isBezier ? point.controlPoint1 : undefined,
              originalControlPoint2: point.isBezier ? point.controlPoint2 : undefined,
            };
            return;
          }
        }
      }

      // Check if clicking on a control point (by checking target name)
      if (targetName && targetName.startsWith("control-point-")) {
        const match = targetName.match(/^control-point-(\d+)-(\d+)$/);
        if (match) {
          const pointIndex = Number.parseInt(match[1], 10);
          const controlIndex = Number.parseInt(match[2], 10);
          if (pointIndex >= 0 && pointIndex < initialPoints.length) {
            const point = initialPoints[pointIndex];
            if (point.isBezier && controlIndex >= 1 && controlIndex <= 2) {
              const controlPoint = controlIndex === 1 ? point.controlPoint1 : point.controlPoint2;
              if (controlPoint) {
                setDraggedControlPoint({ pointIndex, controlIndex });
                isDragging.current = true;
                handleTransformStart();
                lastPos.current = {
                  x: e.evt.clientX,
                  y: e.evt.clientY,
                  originalX: controlPoint.x,
                  originalY: controlPoint.y,
                };
                return;
              }
            }
          }
        }
      }

      // Fallback: check by distance (in case names don't match)
      const scale = transform.zoom * fitScale;
      const hitRadius = HIT_RADIUS.SELECTION / scale;

      for (let i = 0; i < initialPoints.length; i++) {
        const point = initialPoints[i];
        const distance = Math.sqrt((imagePos.x - point.x) ** 2 + (imagePos.y - point.y) ** 2);

        if (distance <= hitRadius) {
          // If cmd-click, handle selection immediately and don't set up dragging
          if (e.evt.ctrlKey || e.evt.metaKey) {
            // Prevent event from propagating to avoid region deselection
            e.evt.stopPropagation();

            // Create a mock event object for the selection handlers
            const mockEvent = {
              ...e,
              target: e.target,
              evt: e.evt,
            } as Konva.KonvaEventObject<MouseEvent>;

            // Try deselection first
            if (
              handlePointDeselection(mockEvent, {
                instanceId,
                initialPoints,
                transform,
                fitScale,
                x,
                y,
                selectedPoints: effectiveSelectedPoints,
                setSelectedPoints,
                skeletonEnabled,
                setActivePointId,
                setLastAddedPointId,
                lastAddedPointId,
                activePointId,
              } as any)
            ) {
              return;
            }
            // If not deselection, try selection (adding to multi-selection)
            if (
              handlePointSelection(mockEvent, {
                instanceId,
                initialPoints,
                transform,
                fitScale,
                x,
                y,
                selectedPoints: effectiveSelectedPoints,
                setSelectedPoints,
                setSelectedPointIndex,
                skeletonEnabled,
                setActivePointId,
                setLastAddedPointId,
                lastAddedPointId,
                activePointId,
                allowClose,
                isPathClosed: finalIsPathClosed,
                selected,
                onFinish,
              } as any)
            ) {
              return;
            }
          } else {
            // Normal click - prevent event propagation to avoid region deselection
            e.evt.stopPropagation();

            // Store the potential drag target but don't start dragging yet
            // We'll start dragging only if the mouse moves beyond a threshold
            setDraggedPointIndex(i);
            lastPos.current = {
              x: e.evt.clientX,
              y: e.evt.clientY,
              originalX: point.x,
              originalY: point.y,
              originalControlPoint1: point.isBezier ? point.controlPoint1 : undefined,
              originalControlPoint2: point.isBezier ? point.controlPoint2 : undefined,
            };
          }
          return;
        }
      }

      // Check if clicking on a control point
      for (let i = 0; i < initialPoints.length; i++) {
        const point = initialPoints[i];
        if (point.isBezier) {
          if (point.controlPoint1) {
            const distance = Math.sqrt(
              (imagePos.x - point.controlPoint1.x) ** 2 + (imagePos.y - point.controlPoint1.y) ** 2,
            );
            if (distance <= hitRadius) {
              setDraggedControlPoint({ pointIndex: i, controlIndex: 1 });
              isDragging.current = true;
              handleTransformStart();
              lastPos.current = {
                x: e.evt.clientX,
                y: e.evt.clientY,
                originalX: point.controlPoint1.x,
                originalY: point.controlPoint1.y,
              };
              return;
            }
          }

          if (point.controlPoint2) {
            const distance = Math.sqrt(
              (imagePos.x - point.controlPoint2.x) ** 2 + (imagePos.y - point.controlPoint2.y) ** 2,
            );
            if (distance <= hitRadius) {
              setDraggedControlPoint({ pointIndex: i, controlIndex: 2 });
              isDragging.current = true;
              handleTransformStart();
              lastPos.current = {
                x: e.evt.clientX,
                y: e.evt.clientY,
                originalX: point.controlPoint2.x,
                originalY: point.controlPoint2.y,
              };
              return;
            }
          }
        }
      }
    };

    const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
      // Get current values from ref to avoid stale closures
      const {
        initialPoints,
        allowClose,
        finalIsPathClosed,
        pixelSnapping,
        isDraggingNewBezier,
        ghostPointDragInfo,
        draggedPointIndex,
        draggedControlPoint,
        isDraggingShape,
        effectiveSelectedPoints,
        transform,
        fitScale,
        x,
        y,
        width,
        height,
        disabled,
      } = currentValuesRef.current;

      // Prevent all interactions when disabled (but allow cursor position updates for ghost line)
      // Only block dragging and point interactions
      if (disabled && (draggedPointIndex !== null || draggedControlPoint !== null || isDraggingShape)) {
        // Stop any ongoing drags when disabled
        setDraggedPointIndex(null);
        setDraggedControlPoint(null);
        setIsDraggingShape(false);
        return;
      }

      // Always update cursor position first (for ghost line to work everywhere)
      const pos = e.target.getStage()?.getPointerPosition();
      if (!pos) return;

      // Update Shift key state from the event to keep it in sync
      if (e.evt.shiftKey !== isShiftKeyHeld) {
        setIsShiftKeyHeld(e.evt.shiftKey);
      }

      const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);

      // Always update cursor position (even outside bounds) so ghost line can work
      cursorPositionRef.current = imagePos;

      // Use RAF to batch redraw calls for performance
      if (ghostLineRafRef.current) {
        cancelAnimationFrame(ghostLineRafRef.current);
      }
      ghostLineRafRef.current = requestAnimationFrame(() => {
        stage.batchDraw();
      });

      // Recalculate ghost point using the helper function
      // Pass the event's shiftKey state and position for real-time updates
      if (calculateGhostPointRef.current) {
        calculateGhostPointRef.current(e.evt.shiftKey, pos);
      }

      // Handle shape dragging first (if active, allow dragging to continue even outside bounds)
      // Skip individual shape dragging if disabled or in multi-region mode (ImageTransformer handles it)
      if (isDraggingShape && shapeDragStartPos.current && !disabled && !isMultiRegionSelected) {
        // Calculate delta from start position
        const deltaX = imagePos.x - shapeDragStartPos.current.imageX;
        const deltaY = imagePos.y - shapeDragStartPos.current.imageY;

        // Track drag distance
        shapeDragDistance.current = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

        // Apply delta to all points
        const newPoints = initialPoints.map((point, index) => {
          const original = originalPointsPositions.current[index];
          if (!original) return point;

          const newX = original.x + deltaX;
          const newY = original.y + deltaY;

          // Apply pixel snapping if enabled
          const snappedPos = snapToPixel({ x: newX, y: newY }, pixelSnapping);

          const updatedPoint = {
            ...point,
            x: snappedPos.x,
            y: snappedPos.y,
          };

          // Move control points with the anchor point
          if (point.isBezier) {
            if (original.controlPoint1) {
              const cp1X = original.controlPoint1.x + deltaX;
              const cp1Y = original.controlPoint1.y + deltaY;
              const snappedCP1 = snapToPixel({ x: cp1X, y: cp1Y }, pixelSnapping);
              updatedPoint.controlPoint1 = snappedCP1;
            }
            if (original.controlPoint2) {
              const cp2X = original.controlPoint2.x + deltaX;
              const cp2Y = original.controlPoint2.y + deltaY;
              const snappedCP2 = snapToPixel({ x: cp2X, y: cp2Y }, pixelSnapping);
              updatedPoint.controlPoint2 = snappedCP2;
            }
          }

          return updatedPoint;
        });

        // Apply bounds checking to all points
        const constrainedPoints = constrainAnchorPointsToBounds(newPoints as BezierPoint[], { width, height });

        onPointsChange?.(constrainedPoints as BezierPoint[]);
        return; // Don't process other logic when dragging shape
      }

      // If we're dragging a point from this instance, allow dragging to continue
      // even if mouse moves outside our group (user might drag outside bounds)
      const isDraggingFromThisInstance = draggedPointIndex !== null || draggedControlPoint !== null;

      // Check if event target belongs to this instance's group
      // This prevents ghost point snapping from working for other regions
      // But we still update cursor position above so ghost line can work
      const target = e.target;
      let targetGroup: Konva.Node | null = target;
      while (targetGroup && targetGroup !== group && targetGroup.getParent()) {
        targetGroup = targetGroup.getParent();
      }
      const isTargetInGroup = targetGroup === group;
      // Also allow when hovering over empty space (stage or layer)
      const isStageOrLayer = target === stage || target.getParent() === stage;

      // Only process ghost point snapping and dragging if:
      // - Target is in our group, OR
      // - We're hovering over empty space (stage/layer), OR
      // - We're dragging from this instance
      if (!isDraggingFromThisInstance && !isTargetInGroup && !isStageOrLayer) {
        // Clear ghost point when hovering over other regions, but keep cursor position for ghost line
        setGhostPoint(null);
        return;
      }

      // Only process ghost point and other logic if within bounds
      if (imagePos.x >= 0 && imagePos.x <= width && imagePos.y >= 0 && imagePos.y <= height) {
        // Handle ghost point when Shift is held (check event directly for real-time updates)
        // Only show ghost point when region is selected and not disabled
        if (
          e.evt.shiftKey &&
          imagePos &&
          initialPoints.length >= 2 &&
          !isDragging.current &&
          !isDraggingNewBezier &&
          !ghostPointDragInfo?.isDragging &&
          selected &&
          !disabled
        ) {
          const scale = transform.zoom * fitScale;
          const hitRadius = HIT_RADIUS.SELECTION / scale;
          let isOverPoint = false;

          for (let i = 0; i < initialPoints.length; i++) {
            const point = initialPoints[i];
            const distance = Math.sqrt((imagePos.x - point.x) ** 2 + (imagePos.y - point.y) ** 2);

            if (distance <= hitRadius) {
              isOverPoint = true;
              break;
            }
          }

          if (isOverPoint) {
            setGhostPoint(null);
          } else {
            const closestPathPoint = findClosestPointOnPath(imagePos, initialPoints, allowClose, finalIsPathClosed);

            if (closestPathPoint) {
              const snappedGhostPoint = snapToPixel(closestPathPoint.point, pixelSnapping);

              if (closestPathPoint.segmentIndex === initialPoints.length) {
                const lastPoint = initialPoints[initialPoints.length - 1];
                const firstPoint = initialPoints[0];

                const ghostPointData = {
                  x: snappedGhostPoint.x,
                  y: snappedGhostPoint.y,
                  prevPointId: lastPoint.id,
                  nextPointId: firstPoint.id,
                };
                setGhostPoint(ghostPointData);
              } else {
                const currentPoint = initialPoints[closestPathPoint.segmentIndex];
                const prevPoint = currentPoint?.prevPointId
                  ? initialPoints.find((p) => p.id === currentPoint.prevPointId)
                  : null;

                if (currentPoint && prevPoint) {
                  const ghostPointData = {
                    x: snappedGhostPoint.x,
                    y: snappedGhostPoint.y,
                    prevPointId: prevPoint.id,
                    nextPointId: currentPoint.id,
                  };
                  setGhostPoint(ghostPointData);
                }
              }
            } else {
              setGhostPoint(null);
            }
          }
        } else if (!e.evt.shiftKey) {
          setGhostPoint(null);
        }

        // Handle point dragging
        if (draggedPointIndex !== null && lastPos.current && !disabled) {
          if (effectiveSelectedPoints.size > 1) {
            return; // Don't drag when transformer is active
          }

          // Check if we should start dragging
          const dragThreshold = 5;
          const mouseDeltaX = Math.abs(e.evt.clientX - lastPos.current.x);
          const mouseDeltaY = Math.abs(e.evt.clientY - lastPos.current.y);

          if (!isDragging.current && (mouseDeltaX > dragThreshold || mouseDeltaY > dragThreshold)) {
            isDragging.current = true;
            handleTransformStart();
          }

          if (!isDragging.current) {
            return;
          }

          const newPoints = [...initialPoints];
          const draggedPoint = newPoints[draggedPointIndex];

          const originalX = lastPos.current.originalX ?? draggedPoint.x;
          const originalY = lastPos.current.originalY ?? draggedPoint.y;

          const snappedPos = snapToPixel(imagePos, pixelSnapping);
          const finalPos = constrainAnchorPointsToBounds([snappedPos], { width, height })[0];

          newPoints[draggedPointIndex] = {
            ...draggedPoint,
            x: finalPos.x,
            y: finalPos.y,
          };

          // Handle bezier control points
          if (draggedPoint.isBezier) {
            const updatedPoint = newPoints[draggedPointIndex];

            if (updatedPoint.controlPoint1 && lastPos.current.originalControlPoint1) {
              const deltaX = finalPos.x - originalX;
              const deltaY = finalPos.y - originalY;
              const cp1Pos = {
                x: lastPos.current.originalControlPoint1.x + deltaX,
                y: lastPos.current.originalControlPoint1.y + deltaY,
              };
              updatedPoint.controlPoint1 = snapToPixel(cp1Pos, pixelSnapping);
            }

            if (updatedPoint.controlPoint2 && lastPos.current.originalControlPoint2) {
              const deltaX = finalPos.x - originalX;
              const deltaY = finalPos.y - originalY;
              const cp2Pos = {
                x: lastPos.current.originalControlPoint2.x + deltaX,
                y: lastPos.current.originalControlPoint2.y + deltaY,
              };
              updatedPoint.controlPoint2 = snapToPixel(cp2Pos, pixelSnapping);
            }

            const constrainedPoint = constrainAnchorPointsToBounds([updatedPoint], { width, height })[0];
            newPoints[draggedPointIndex] = constrainedPoint;
          }

          onPointsChange?.(newPoints);
          onPointRepositioned?.(newPoints[draggedPointIndex], draggedPointIndex);
        }

        // Handle control point dragging
        if (draggedControlPoint && lastPos.current && !disabled) {
          const newPoints = [...initialPoints];
          const point = newPoints[draggedControlPoint.pointIndex];

          if (point.isBezier) {
            const snappedPos = snapToPixel(imagePos, pixelSnapping);
            const finalPos = constrainPointToBounds(snappedPos, { width, height });

            if (draggedControlPoint.controlIndex === 1) {
              point.controlPoint1 = finalPos;
            } else if (draggedControlPoint.controlIndex === 2) {
              point.controlPoint2 = finalPos;
            }

            newPoints[draggedControlPoint.pointIndex] = point;
            onPointsChange?.(newPoints);
            onPointEdited?.(point, draggedControlPoint.pointIndex);
          }
        }
      } else {
        // When outside bounds, clear ghost point but keep cursor position for ghost line
        setGhostPoint(null);
      }
    };

    const handleStageMouseUp = (e: Konva.KonvaEventObject<MouseEvent>) => {
      // Get current values from ref to avoid stale closures
      const {
        isDraggingShape,
        draggedPointIndex,
        initialPoints,
        effectiveSelectedPoints,
        instanceId,
        skeletonEnabled,
        activePointId,
        disabled,
        onFinish,
      } = currentValuesRef.current;

      // Prevent all interactions when disabled
      if (disabled) {
        return;
      }

      // Handle shape dragging end
      if (isDraggingShape) {
        const dragThreshold = 5; // Only prevent clicks if we actually dragged
        const actuallyDragged = shapeDragDistance.current > dragThreshold;

        setIsDraggingShape(false);
        handleTransformEnd(e);
        shapeDragStartPos.current = null;
        originalPointsPositions.current = [];

        // Only prevent click handler from adding a point if we actually dragged
        if (actuallyDragged) {
          justFinishedShapeDrag.current = true;
          // Prevent event propagation to avoid triggering click handlers
          e.evt.stopPropagation();
          e.evt.preventDefault();
          // Don't use setTimeout - let the click handlers clear the flag
        }

        shapeDragDistance.current = 0;
        return; // Don't process point selection when ending shape drag
      }

      // Handle point selection if we clicked but didn't drag
      if (draggedPointIndex !== null && !isDragging.current) {
        // Use handlePointSelectionFromIndex to properly handle selection through tracker
        handlePointSelectionFromIndex(
          draggedPointIndex,
          {
            instanceId,
            initialPoints,
            selectedPoints: effectiveSelectedPoints,
            setSelectedPoints,
            skeletonEnabled,
            setActivePointId,
            activePointId,
            onFinish,
          } as any,
          e,
        );
        onPointSelected?.(draggedPointIndex);
      }

      // Reset dragging state
      isDragging.current = false;
      handleTransformEnd(e);
      setDraggedPointIndex(null);
      setDraggedControlPoint(null);
    };

    const handleStageMouseEnter = (e: Konva.KonvaEventObject<MouseEvent>) => {
      // Capture cursor position when mouse enters stage so ghost line can render immediately
      const pos = e.target.getStage()?.getPointerPosition();
      if (pos) {
        const { transform, fitScale, x, y } = currentValuesRef.current;
        const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
        cursorPositionRef.current = imagePos;
        // Trigger a redraw to show ghost line
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        ghostLineRafRef.current = requestAnimationFrame(() => {
          stage.batchDraw();
        });
      }
    };

    const handleStageMouseLeave = () => {
      cursorPositionRef.current = null;
      setGhostPoint(null);
    };

    if (disableInternalPointAddition) {
      stage.on("mousedown", handleStageMouseDown);
      stage.on("mousemove", handleStageMouseMove);
      stage.on("mouseup", handleStageMouseUp);
      stage.on("mouseenter", handleStageMouseEnter);
      stage.on("mouseleave", handleStageMouseLeave);

      // Try to initialize cursor position if mouse is already over the stage
      // This ensures ghost line can render immediately even if mouseenter didn't fire
      const tryInitializeCursorPosition = () => {
        const pos = stage.getPointerPosition();
        if (pos) {
          const { transform, fitScale, x, y } = currentValuesRef.current;
          const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
          cursorPositionRef.current = imagePos;
          // Trigger a redraw to show ghost line
          if (ghostLineRafRef.current) {
            cancelAnimationFrame(ghostLineRafRef.current);
          }
          ghostLineRafRef.current = requestAnimationFrame(() => {
            stage.batchDraw();
          });
        }
      };

      // Try to initialize immediately
      tryInitializeCursorPosition();

      // Also try after a short delay in case the stage isn't ready yet
      const initTimeout = setTimeout(() => {
        tryInitializeCursorPosition();
      }, 0);

      return () => {
        clearTimeout(initTimeout);
        handlersAttachedRef.current = false;
        stage.off("mousedown", handleStageMouseDown);
        stage.off("mousemove", handleStageMouseMove);
        stage.off("mouseup", handleStageMouseUp);
        stage.off("mouseenter", handleStageMouseEnter);
        stage.off("mouseleave", handleStageMouseLeave);
      };
    }
    // Handle cursor position, ghost point, and shape dragging
    stage.on("mousemove", handleStageMouseMove);
    stage.on("mouseup", handleStageMouseUp);
    stage.on("mouseenter", handleStageMouseEnter);
    stage.on("mouseleave", handleStageMouseLeave);

    // Try to initialize cursor position if mouse is already over the stage
    // This ensures ghost line can render immediately even if mouseenter didn't fire
    const tryInitializeCursorPosition = () => {
      const pos = stage.getPointerPosition();
      if (pos) {
        const { transform, fitScale, x, y } = currentValuesRef.current;
        const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
        cursorPositionRef.current = imagePos;
        // Trigger a redraw to show ghost line
        if (ghostLineRafRef.current) {
          cancelAnimationFrame(ghostLineRafRef.current);
        }
        ghostLineRafRef.current = requestAnimationFrame(() => {
          stage.batchDraw();
        });
      }
    };

    // Try to initialize immediately
    tryInitializeCursorPosition();

    // Also try after a short delay in case the stage isn't ready yet
    const initTimeout = setTimeout(() => {
      tryInitializeCursorPosition();
    }, 0);

    return () => {
      clearTimeout(initTimeout);
      handlersAttachedRef.current = false;
      if (ghostLineRafRef.current) {
        cancelAnimationFrame(ghostLineRafRef.current);
      }
      stage.off("mousemove", handleStageMouseMove);
      stage.off("mouseup", handleStageMouseUp);
      stage.off("mouseenter", handleStageMouseEnter);
      stage.off("mouseleave", handleStageMouseLeave);
    };
  }, [disableInternalPointAddition, stageReadyRetry]); // Re-run when disableInternalPointAddition changes or when retrying

  // Handle Shift key for disconnected mode
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Shift") {
        setIsDisconnectedMode(true);
      }
    };

    const handleKeyUp = (e: KeyboardEvent) => {
      if (e.key === "Shift") {
        setIsDisconnectedMode(false);
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    window.addEventListener("keyup", handleKeyUp);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
      window.removeEventListener("keyup", handleKeyUp);
    };
  }, []);

  // Click handler with debouncing for single/double-click detection
  const handleClickWithDebouncing = useCallback(
    (e: any, onClickHandler?: (e: any) => void, onDblClickHandler?: (e: any) => void) => {
      // If disabled or not selected, fire onClick immediately (no need to wait for double-click detection)
      if (disabled || !selected) {
        if (onClickHandler) {
          const newEvent = {
            ...e,
            evt: {
              ...e.evt,
              defaultPrevented: false,
              stopImmediatePropagation: e.evt.stopImmediatePropagation?.bind(e.evt) || (() => {}),
              stopPropagation: e.evt.stopPropagation?.bind(e.evt) || (() => {}),
              preventDefault: e.evt.preventDefault?.bind(e.evt) || (() => {}),
            },
          };
          onClickHandler(newEvent);
        }
        return;
      }

      // Clear any existing timeout
      if (clickTimeoutRef.current) {
        clearTimeout(clickTimeoutRef.current);
        clickTimeoutRef.current = null;
        // This is a double-click, handle it
        doubleClickHandledRef.current = true;
        if (onDblClickHandler) {
          // Create a new event object to avoid defaultPrevented issues
          // Preserve all event methods by copying the original evt object and only resetting defaultPrevented
          const newEvent = {
            ...e,
            evt: {
              ...e.evt,
              defaultPrevented: false,
              // Preserve all methods from the original event
              stopImmediatePropagation: e.evt.stopImmediatePropagation?.bind(e.evt) || (() => {}),
              stopPropagation: e.evt.stopPropagation?.bind(e.evt) || (() => {}),
              preventDefault: e.evt.preventDefault?.bind(e.evt) || (() => {}),
            },
          };
          onDblClickHandler(newEvent);
        }
        // Reset the flag after a short delay
        setTimeout(() => {
          doubleClickHandledRef.current = false;
        }, 100);
        return;
      }

      // Set a timeout for single-click handling (only when selected, to detect double-clicks)
      clickTimeoutRef.current = setTimeout(() => {
        clickTimeoutRef.current = null;
        if (onClickHandler) {
          // Create a new event object to avoid defaultPrevented issues
          // This ensures the onClick handler in VectorRegion.jsx can fire even if
          // the event was prevented elsewhere (e.g., by onFinish handler)
          // Preserve all event methods by copying the original evt object and only resetting defaultPrevented
          const newEvent = {
            ...e,
            evt: {
              ...e.evt,
              defaultPrevented: false,
              // Preserve all methods from the original event
              stopImmediatePropagation: e.evt.stopImmediatePropagation?.bind(e.evt) || (() => {}),
              stopPropagation: e.evt.stopPropagation?.bind(e.evt) || (() => {}),
              preventDefault: e.evt.preventDefault?.bind(e.evt) || (() => {}),
            },
          };
          onClickHandler(newEvent);
        }
      }, 200);
    },
    [selected, disabled],
  );

  // Create event handlers
  const eventHandlers = createEventHandlers({
    instanceId,
    initialPoints,
    width,
    height,
    pixelSnapping,
    selectedPoints: effectiveSelectedPoints,
    selectedPointIndex,
    setSelectedPointIndex,
    setSelectedPoints,
    setDraggedPointIndex,
    setDraggedControlPoint,
    setIsDisconnectedMode,
    setGhostPoint,
    setNewPointDragIndex,
    setIsDraggingNewBezier,
    setGhostPointDragInfo,
    setVisibleControlPoints,
    setIsPathClosed,
    isDragging,
    lastPos,
    lastCallbackTime,
    isDrawingMode: !drawingDisabled, // Use dynamic drawing detection
    allowClose,
    allowBezier,
    isPathClosed: finalIsPathClosed,
    transform,
    fitScale,
    x,
    y,
    ghostPoint,
    ghostPointDragInfo,
    draggedPointIndex,
    draggedControlPoint,
    isDraggingNewBezier,
    newPointDragIndex: _newPointDragIndex,
    visibleControlPoints,
    isDisconnectedMode,
    onPointsChange,
    onPointAdded,
    onPointRemoved,
    onPointEdited,
    onPointRepositioned,
    onPointConverted,
    onPathShapeChanged,
    onPointSelected,
    onFinish,
    onGhostPointClick,
    onMouseDown,
    onMouseMove,
    onMouseUp,
    onClick,
    notifyTransformationComplete,
    canAddMorePoints,
    maxPoints,
    minPoints, // Add minPoints to event handlers
    skeletonEnabled,
    getAllPoints,
    getPointInfo,
    updatePointByGlobalIndex,
    lastAddedPointId,
    setLastAddedPointId,
    activePointId,
    setActivePointId,
    isTransforming,
    selected,
    disabled,
    transformMode,
    disableInternalPointAddition,
    handleTransformStart,
    handleTransformEnd,
    pointCreationManager,
  });

  return (
    <Group
      ref={stageRef}
      scaleX={scaleX}
      scaleY={scaleY}
      x={x}
      y={y}
      imageSmoothingEnabled={imageSmoothingEnabled}
      onMouseDown={selected && !disabled ? eventHandlers.handleLayerMouseDown : undefined}
      onMouseMove={selected && !disabled ? eventHandlers.handleLayerMouseMove : undefined}
      onMouseUp={selected && !disabled ? eventHandlers.handleLayerMouseUp : undefined}
      onClick={
        !selected || transformMode
          ? undefined
          : (e) => {
              // Prevent editing when disabled, but allow selection clicks
              // Prevent all clicks when in transform mode (already checked above, but double-check)
              if (transformMode) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.evt.stopImmediatePropagation();
                e.cancelBubble = true;
                return;
              }

              // When disabled, only allow selection clicks - skip all editing logic
              if (disabled) {
                // Call handleClickWithDebouncing to trigger selection via onClick handler
                // This allows the shape to be selected even when disabled
                handleClickWithDebouncing(e, onClick, onDblClick);
                return;
              }

              // Don't add points if we just finished shape dragging
              if (justFinishedShapeDrag.current) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true; // Also set cancelBubble for Konva
                justFinishedShapeDrag.current = false; // Clear flag immediately
                return;
              }

              // Skip if point selection was already handled by VectorPoints onClick
              if (pointSelectionHandled.current) {
                pointSelectionHandled.current = false;
                // Don't prevent propagation - let the event bubble to VectorShape onClick
                return;
              }

              // For the first point in drawing mode, we need to ensure the click handler works
              // The issue is that the flag logic is interfering with first point creation
              // Let's try calling the drawing mode click handler directly for the first point
              // Skip if internal point addition is disabled
              if (initialPoints.length === 0 && !drawingDisabled && !disableInternalPointAddition) {
                // For the first point, call the drawing mode click handler directly
                const pos = e.target.getStage()?.getPointerPosition();
                if (pos) {
                  // Use the same coordinate transformation as the event handlers
                  const imagePos = {
                    x: (pos.x - x - transform.offsetX) / (scaleX * transform.zoom * fitScale),
                    y: (pos.y - y - transform.offsetY) / (scaleY * transform.zoom * fitScale),
                  };

                  // Check if we're within canvas bounds
                  if (imagePos.x >= 0 && imagePos.x <= width && imagePos.y >= 0 && imagePos.y <= height) {
                    // Create the first point directly
                    const newPoint = {
                      id: `point-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
                      x: imagePos.x,
                      y: imagePos.y,
                      isBezier: false,
                    };

                    const newPoints = [...initialPoints, newPoint];
                    onPointsChange?.(newPoints);
                    onPointAdded?.(newPoint, newPoints.length - 1);

                    // Set as the last added point
                    setLastAddedPointId(newPoint.id);
                    setActivePointId(newPoint.id);

                    return;
                  }
                }
              }

              // For subsequent points, use the normal event handler
              // But don't prevent propagation - let the event bubble to VectorShape onClick
              // so that shape selection/unselection can work when clicking on segments
              eventHandlers.handleLayerClick(e);
              // Don't call preventDefault or stopPropagation here - let the event bubble
            }
      }
      onDblClick={
        !selected || disabled
          ? undefined
          : (e) => {
              // If we've already handled this double-click through debouncing, ignore it
              if (doubleClickHandledRef.current) {
                return;
              }
              // Otherwise, call the original onDblClick handler
              onDblClick?.(e);
            }
      }
    >
      {/* Invisible rectangle - render to capture mouse events for cursor position updates */}
      {/* Disabled when disableInternalPointAddition is true, component is not selected, or disabled */}
      {selected && !disabled && !disableInternalPointAddition && (
        <Shape
          sceneFunc={(ctx, shape) => {
            ctx.beginPath();
            ctx.rect(0, 0, width, height);
            ctx.fillShape(shape);
          }}
          fill={INVISIBLE_SHAPE_OPACITY}
        />
      )}

      {/* Conditionally wrap content with _transformable group for ImageTransformer */}
      {isMultiRegionSelected ? (
        <Group
          name="_transformable"
          ref={transformableGroupRef}
          draggable={!disabled}
          onTransformEnd={(e) => {
            // Prevent transform when disabled
            if (disabled) return;
            // This is called when ImageTransformer finishes transforming the Group
            // Commit the transform immediately to prevent position reset
            if (e.target === e.currentTarget && transformableGroupRef.current && initialTransformRef.current) {
              commitMultiRegionTransform();
              handleTransformEnd(e);
            }
          }}
        >
          {/* Unified vector shape - renders all lines based on id-prevPointId relationships */}
          <VectorShape
            segments={getAllLineSegments()}
            allowClose={allowClose}
            isPathClosed={finalIsPathClosed}
            stroke={stroke}
            fill={fill}
            strokeWidth={props.strokeWidth}
            opacity={props.opacity}
            transform={transform}
            fitScale={fitScale}
            onClick={(e) => {
              // Prevent editing clicks when disabled, but allow selection clicks
              // Prevent all clicks when in transform mode
              if (transformMode) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.evt.stopImmediatePropagation();
                e.cancelBubble = true;
                return;
              }

              // When disabled, only allow selection clicks - skip all editing logic
              if (disabled) {
                // Allow the click to bubble for selection - don't prevent propagation
                // Use debouncing for click/double-click detection for selection
                if (!justFinishedShapeDrag.current && !e.evt.shiftKey) {
                  handleClickWithDebouncing(e, onClick, onDblClick);
                }
                return;
              }

              // CRITICAL: Handle Alt+click FIRST (for point deletion and segment breaking)
              // This must happen before any other click handling to ensure deletion works
              if (e.evt.altKey && !e.evt.shiftKey && selected) {
                // Let the event bubble to the Group onClick handler which has the Alt+click logic
                // Don't stop propagation or prevent default - let it reach createClickHandler
                return;
              }

              // Don't add points if we just finished shape dragging
              if (justFinishedShapeDrag.current) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true; // Also set cancelBubble for Konva
                justFinishedShapeDrag.current = false; // Clear flag immediately
                return;
              }

              // Check if click is on the last added point by checking cursor position
              if (cursorPositionRef.current && lastAddedPointId) {
                const lastAddedPoint = initialPoints.find((p) => p.id === lastAddedPointId);
                if (lastAddedPoint) {
                  const scale = transform.zoom * fitScale;
                  const hitRadius = HIT_RADIUS.SELECTION / scale; // Use constant for consistent hit detection
                  const distance = Math.sqrt(
                    (cursorPositionRef.current.x - lastAddedPoint.x) ** 2 +
                      (cursorPositionRef.current.y - lastAddedPoint.y) ** 2,
                  );

                  if (distance <= hitRadius) {
                    // Find the index of the last added point
                    const lastAddedPointIndex = initialPoints.findIndex((p) => p.id === lastAddedPointId);

                    // Only trigger onFinish if the last added point is already selected (second click)
                    // and no modifiers are pressed (ctrl, meta, shift, alt) and component is selected
                    // and not in transform mode
                    if (
                      lastAddedPointIndex !== -1 &&
                      effectiveSelectedPoints.has(lastAddedPointIndex) &&
                      selected &&
                      !transformMode
                    ) {
                      const hasModifiers = e.evt.ctrlKey || e.evt.metaKey || e.evt.shiftKey || e.evt.altKey;
                      if (!hasModifiers) {
                        e.evt.preventDefault();
                        onFinish?.(e);
                        return;
                      }
                      // If modifiers are held, skip onFinish entirely and let normal modifier handling take over
                      return;
                    }
                  }
                }
              }

              // Use debouncing for click/double-click detection
              // This will call the onClick handler from VectorRegion.jsx which handles shape selection/unselection
              // Only call if we didn't just finish dragging (to allow shape dragging to work)
              // Don't call if Shift is held (to allow shift-click for ghost point insertion without unselecting)
              if (!justFinishedShapeDrag.current && !e.evt.shiftKey) {
                // Stop propagation to prevent the Group onClick handler from also processing the click
                // This prevents the shape from being selected and then immediately unselected
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                handleClickWithDebouncing(e, onClick, onDblClick);
              }
            }}
            onMouseDown={(e) => {
              // Don't start shape drag if disabled
              if (disabled) {
                return;
              }
              // Don't start shape drag if in multi-region selection mode
              // ImageTransformer will handle dragging in this case
              if (isMultiRegionSelected) {
                return;
              }

              // Don't start shape drag if we're already dragging a point or control point
              if (draggedPointIndex !== null || draggedControlPoint !== null) {
                return;
              }

              // Don't start shape drag if transformer is active
              if (effectiveSelectedPoints.size > 1) {
                return;
              }

              // Don't start shape drag if clicking on a point
              const pos = e.target.getStage()?.getPointerPosition();
              if (!pos) return;

              const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
              const scale = transform.zoom * fitScale;
              const hitRadius = HIT_RADIUS.SELECTION / scale;

              // Check if clicking on any point
              for (let i = 0; i < initialPoints.length; i++) {
                const point = initialPoints[i];
                const distance = Math.sqrt((imagePos.x - point.x) ** 2 + (imagePos.y - point.y) ** 2);
                if (distance <= hitRadius) {
                  return; // Let point dragging handle it
                }
              }

              // Check if clicking on any control point
              for (let i = 0; i < initialPoints.length; i++) {
                const point = initialPoints[i];
                if (point.isBezier) {
                  if (point.controlPoint1) {
                    const distance = Math.sqrt(
                      (imagePos.x - point.controlPoint1.x) ** 2 + (imagePos.y - point.controlPoint1.y) ** 2,
                    );
                    if (distance <= hitRadius) {
                      return; // Let control point dragging handle it
                    }
                  }
                  if (point.controlPoint2) {
                    const distance = Math.sqrt(
                      (imagePos.x - point.controlPoint2.x) ** 2 + (imagePos.y - point.controlPoint2.y) ** 2,
                    );
                    if (distance <= hitRadius) {
                      return; // Let control point dragging handle it
                    }
                  }
                }
              }

              // Start shape dragging (don't stop propagation yet - we'll do it on mouseup if we actually drag)
              setIsDraggingShape(true);
              handleTransformStart();
              shapeDragDistance.current = 0; // Reset drag distance
              shapeDragStartPos.current = {
                x: e.evt.clientX,
                y: e.evt.clientY,
                imageX: imagePos.x,
                imageY: imagePos.y,
              };

              // Store original positions of all points
              originalPointsPositions.current = initialPoints.map((point) => ({
                x: point.x,
                y: point.y,
                controlPoint1: point.controlPoint1 ? { x: point.controlPoint1.x, y: point.controlPoint1.y } : undefined,
                controlPoint2: point.controlPoint2 ? { x: point.controlPoint2.x, y: point.controlPoint2.y } : undefined,
              }));
            }}
            onMouseEnter={onMouseEnter}
            onMouseLeave={onMouseLeave}
            key={`vector-shape-${initialPoints.length}-${initialPoints.map((p) => p.id).join("-")}`}
          />

          {/* Ghost line - preview from last point to cursor */}
          {selected && !disabled && !disableGhostLine && (
            <GhostLine
              initialPoints={initialPoints}
              cursorPositionRef={cursorPositionRef}
              draggedControlPoint={draggedControlPoint}
              draggedPointIndex={draggedPointIndex}
              isDraggingNewBezier={isDraggingNewBezier}
              isPathClosed={finalIsPathClosed}
              allowClose={allowClose}
              transform={transform}
              fitScale={fitScale}
              maxPoints={maxPoints}
              minPoints={minPoints}
              skeletonEnabled={skeletonEnabled}
              selectedPointIndex={selectedPointIndex}
              lastAddedPointId={lastAddedPointId}
              activePointId={activePointId}
              stroke={stroke}
              pixelSnapping={pixelSnapping}
              drawingDisabled={drawingDisabled}
              isShiftKeyHeld={isShiftKeyHeld}
              transformMode={transformMode}
              effectiveSelectedPointsSize={effectiveSelectedPoints.size}
            />
          )}

          {/* Control points - render first so lines appear under main points */}
          {selected && !disabled && (
            <ControlPoints
              initialPoints={getAllPoints()}
              selectedPointIndex={selectedPointIndex}
              isDraggingNewBezier={isDraggingNewBezier}
              draggedControlPoint={draggedControlPoint}
              visibleControlPoints={visibleControlPoints}
              transform={transform}
              fitScale={fitScale}
              key={`control-points-${initialPoints.length}-${initialPoints.map((p, i) => `${i}-${p.x.toFixed(1)}-${p.y.toFixed(1)}-${p.controlPoint1?.x?.toFixed(1) || "null"}-${p.controlPoint1?.y?.toFixed(1) || "null"}-${p.controlPoint2?.x?.toFixed(1) || "null"}-${p.controlPoint2?.y?.toFixed(1) || "null"}`).join("-")}`}
            />
          )}

          {/* All vector points */}
          <VectorPoints
            initialPoints={getAllPoints()}
            selectedPointIndex={selectedPointIndex}
            selectedPoints={effectiveSelectedPoints}
            transform={transform}
            fitScale={fitScale}
            pointRefs={pointRefs}
            selected={selected}
            disabled={disabled}
            transformMode={transformMode}
            pointRadius={pointRadius}
            pointFill={pointFill}
            pointStroke={pointStroke}
            pointStrokeSelected={pointStrokeSelected}
            pointStrokeWidth={pointStrokeWidth}
            activePointId={activePointId}
            maxPoints={maxPoints}
            onPointClick={(e, pointIndex) => {
              // Prevent all clicks when disabled or in transform mode
              if (disabled || transformMode) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.evt.stopImmediatePropagation();
                e.cancelBubble = true;
                return;
              }

              // CRITICAL: For single-point regions, directly call onClick handler so the region can be selected
              // Single-point regions have no segments to click on, so clicking the point must trigger region selection
              // Check this FIRST before any other logic
              // BUT: Don't do this in transform mode - clicks must be completely disabled
              const isSinglePointRegion = initialPoints.length === 1;
              if (
                isSinglePointRegion &&
                !e.evt.altKey &&
                !e.evt.shiftKey &&
                !e.evt.ctrlKey &&
                !e.evt.metaKey &&
                !transformMode
              ) {
                // Select the point first
                tracker.selectPoints(instanceId, new Set([pointIndex]));
                // Directly call handleClickWithDebouncing to trigger region selection
                // This works even when selected=false (Group onClick is undefined)
                pointSelectionHandled.current = true;
                handleClickWithDebouncing(e, onClick, onDblClick);
                return;
              }

              // Handle point selection even when not selected (similar to shape clicks)
              // But never allow selection when disabled
              if (disabled) {
                return;
              }
              if (!selected) {
                // Check if this instance can have selection
                if (!tracker.canInstanceHaveSelection(instanceId)) {
                  return; // Block the selection
                }

                // Check if we're about to close the path - prevent point selection in this case
                if (
                  shouldClosePathOnPointClick(
                    pointIndex,
                    {
                      initialPoints,
                      allowClose,
                      isPathClosed: finalIsPathClosed,
                      skeletonEnabled,
                      activePointId,
                    } as any,
                    e,
                  ) &&
                  isActivePointEligibleForClosing({
                    initialPoints,
                    skeletonEnabled,
                    activePointId,
                  } as any)
                ) {
                  // Use the bidirectional closePath function
                  const success = (ref as React.MutableRefObject<KonvaVectorRef | null>)?.current?.close();
                  if (success) {
                    return; // Path was closed, don't select the point
                  }
                }

                // Handle cmd-click to select all points (only when not in transform mode)
                if (!transformMode && (e.evt.ctrlKey || e.evt.metaKey) && !e.evt.altKey && !e.evt.shiftKey) {
                  // Select all points in the path
                  const allPointIndices = Array.from({ length: initialPoints.length }, (_, i) => i);
                  tracker.selectPoints(instanceId, new Set(allPointIndices));
                  pointSelectionHandled.current = true; // Mark that we handled selection
                  e.evt.stopImmediatePropagation(); // Prevent all other handlers from running
                  return;
                }

                // Check if this is the last added point and already selected (second click)
                // Only check if the shape is selected - non-selected shapes should not trigger onFinish
                if (selected) {
                  const isLastAddedPoint = lastAddedPointId && initialPoints[pointIndex]?.id === lastAddedPointId;
                  const isAlreadySelected = effectiveSelectedPoints.has(pointIndex);

                  // Only fire onFinish if this is the last added point AND it was already selected (second click)
                  // and no modifiers are pressed (ctrl, meta, shift, alt) and we're in drawing mode
                  // and not in transform mode
                  if (isLastAddedPoint && isAlreadySelected && !drawingDisabled && !transformMode) {
                    const hasModifiers = e.evt.ctrlKey || e.evt.metaKey || e.evt.shiftKey || e.evt.altKey;
                    if (!hasModifiers) {
                      onFinish?.(e);
                      pointSelectionHandled.current = true; // Mark that we handled selection
                      e.evt.stopImmediatePropagation(); // Prevent all other handlers from running
                      return;
                    }
                    // If modifiers are held, skip onFinish entirely and let normal modifier handling take over
                    return;
                  }
                }

                // Handle regular point selection (only when not in transform mode)
                if (!transformMode) {
                  if (e.evt.ctrlKey || e.evt.metaKey) {
                    // Add to multi-selection
                    const newSelection = new Set(selectedPoints);
                    newSelection.add(pointIndex);
                    tracker.selectPoints(instanceId, newSelection);
                  } else {
                    // Select only this point
                    tracker.selectPoints(instanceId, new Set([pointIndex]));
                  }
                }

                // Don't call onClick here - clicking on points should NOT select/unselect the shape
                // Only clicking on segments should select/unselect the shape
                // EXCEPTION: Single-point regions (handled above)

                // Mark that we handled selection and prevent all other handlers from running
                // This prevents the VectorShape onClick handler from firing, which would call onFinish
                pointSelectionHandled.current = true;
                e.evt.stopImmediatePropagation();
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                return;
              }

              // When selected, let the normal event handlers handle it
              // The point click will be detected by the layer-level handlers
              //
            }}
          />

          {/* Proxy nodes for Transformer (positioned at exact point centers) - only show when not in drawing mode */}
          {drawingDisabled && (
            <ProxyNodes selectedPoints={effectiveSelectedPoints} initialPoints={getAllPoints()} proxyRefs={proxyRefs} />
          )}

          {/* Transformer for multiselection - only show when not in drawing mode and not multi-region selected */}
          {drawingDisabled && !isMultiRegionSelected && (
            <VectorTransformer
              selectedPoints={selectedPoints}
              initialPoints={getAllPoints()}
              transformerRef={transformerRef}
              proxyRefs={proxyRefs}
              bounds={{
                x: 0,
                y: 0,
                width: width,
                height: height,
              }}
              scaleX={scaleX}
              scaleY={scaleY}
              transform={transform}
              fitScale={fitScale}
              getCurrentPointsRef={getCurrentPointsRef}
              updateCurrentPointsRef={updateCurrentPointsRef}
              pixelSnapping={pixelSnapping}
              onPointsChange={(newPoints) => {
                // Update main path points
                onPointsChange?.(newPoints);
              }}
              onTransformStateChange={(state) => {
                transformerStateRef.current = state;
              }}
              onTransformationStart={() => {
                setIsTransforming(true);
                handleTransformStart();
              }}
              onTransformationEnd={() => {
                setIsTransforming(false);
                handleTransformEnd();
              }}
            />
          )}
        </Group>
      ) : (
        <>
          {/* Unified vector shape - renders all lines based on id-prevPointId relationships */}
          <VectorShape
            segments={getAllLineSegments()}
            allowClose={allowClose}
            isPathClosed={finalIsPathClosed}
            stroke={stroke}
            fill={fill}
            strokeWidth={props.strokeWidth}
            opacity={props.opacity}
            transform={transform}
            fitScale={fitScale}
            onClick={(e) => {
              // Prevent editing clicks when disabled, but allow selection clicks
              // Prevent all clicks when in transform mode
              if (transformMode) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.evt.stopImmediatePropagation();
                e.cancelBubble = true;
                return;
              }

              // When disabled, only allow selection clicks - skip all editing logic
              if (disabled) {
                // Allow the click to bubble for selection - don't prevent propagation
                // Use debouncing for click/double-click detection for selection
                if (!justFinishedShapeDrag.current && !e.evt.shiftKey) {
                  handleClickWithDebouncing(e, onClick, onDblClick);
                }
                return;
              }

              // CRITICAL: Handle Alt+click FIRST (for point deletion and segment breaking)
              // This must happen before any other click handling to ensure deletion works
              if (e.evt.altKey && !e.evt.shiftKey && selected) {
                // Let the event bubble to the Group onClick handler which has the Alt+click logic
                // Don't stop propagation or prevent default - let it reach createClickHandler
                return;
              }

              // Don't add points if we just finished shape dragging
              if (justFinishedShapeDrag.current) {
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true; // Also set cancelBubble for Konva
                justFinishedShapeDrag.current = false; // Clear flag immediately
                return;
              }

              // Check if click is on the last added point by checking cursor position
              if (cursorPositionRef.current && lastAddedPointId) {
                const lastAddedPoint = initialPoints.find((p) => p.id === lastAddedPointId);
                if (lastAddedPoint) {
                  const scale = transform.zoom * fitScale;
                  const hitRadius = HIT_RADIUS.SELECTION / scale; // Use constant for consistent hit detection
                  const distance = Math.sqrt(
                    (cursorPositionRef.current.x - lastAddedPoint.x) ** 2 +
                      (cursorPositionRef.current.y - lastAddedPoint.y) ** 2,
                  );

                  if (distance <= hitRadius) {
                    // Find the index of the last added point
                    const lastAddedPointIndex = initialPoints.findIndex((p) => p.id === lastAddedPointId);

                    // Only trigger onFinish if the last added point is already selected (second click)
                    // and no modifiers are pressed (ctrl, meta, shift, alt) and component is selected
                    // and not in transform mode
                    if (
                      lastAddedPointIndex !== -1 &&
                      effectiveSelectedPoints.has(lastAddedPointIndex) &&
                      selected &&
                      !transformMode
                    ) {
                      const hasModifiers = e.evt.ctrlKey || e.evt.metaKey || e.evt.shiftKey || e.evt.altKey;
                      if (!hasModifiers) {
                        e.evt.preventDefault();
                        onFinish?.(e);
                        return;
                      }
                      // If modifiers are held, skip onFinish entirely and let normal modifier handling take over
                      return;
                    }
                  }
                }
              }

              // Use debouncing for click/double-click detection
              // This will call the onClick handler from VectorRegion.jsx which handles shape selection/unselection
              // Only call if we didn't just finish dragging (to allow shape dragging to work)
              // Don't call if Shift is held (to allow shift-click for ghost point insertion without unselecting)
              if (!justFinishedShapeDrag.current && !e.evt.shiftKey) {
                // Stop propagation to prevent the Group onClick handler from also processing the click
                // This prevents the shape from being selected and then immediately unselected
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                handleClickWithDebouncing(e, onClick, onDblClick);
              }
            }}
            onMouseDown={(e) => {
              // Don't start shape drag if disabled
              if (disabled) {
                return;
              }
              // Don't start shape drag if in multi-region selection mode
              // ImageTransformer will handle dragging in this case
              if (isMultiRegionSelected) {
                return;
              }

              // Don't start shape drag if we're already dragging a point or control point
              if (draggedPointIndex !== null || draggedControlPoint !== null) {
                return;
              }

              // Don't start shape drag if transformer is active
              if (effectiveSelectedPoints.size > 1) {
                return;
              }

              // Don't start shape drag if clicking on a point
              const pos = e.target.getStage()?.getPointerPosition();
              if (!pos) return;

              const imagePos = stageToImageCoordinates(pos, transform, fitScale, x, y);
              const scale = transform.zoom * fitScale;
              const hitRadius = HIT_RADIUS.SELECTION / scale;

              // Check if clicking on any point
              for (let i = 0; i < initialPoints.length; i++) {
                const point = initialPoints[i];
                const distance = Math.sqrt((imagePos.x - point.x) ** 2 + (imagePos.y - point.y) ** 2);
                if (distance <= hitRadius) {
                  return; // Let point dragging handle it
                }
              }

              // Check if clicking on any control point
              for (let i = 0; i < initialPoints.length; i++) {
                const point = initialPoints[i];
                if (point.isBezier) {
                  if (point.controlPoint1) {
                    const distance = Math.sqrt(
                      (imagePos.x - point.controlPoint1.x) ** 2 + (imagePos.y - point.controlPoint1.y) ** 2,
                    );
                    if (distance <= hitRadius) {
                      return; // Let control point dragging handle it
                    }
                  }
                  if (point.controlPoint2) {
                    const distance = Math.sqrt(
                      (imagePos.x - point.controlPoint2.x) ** 2 + (imagePos.y - point.controlPoint2.y) ** 2,
                    );
                    if (distance <= hitRadius) {
                      return; // Let control point dragging handle it
                    }
                  }
                }
              }

              // Start shape dragging (don't stop propagation yet - we'll do it on mouseup if we actually drag)
              setIsDraggingShape(true);
              handleTransformStart();
              shapeDragDistance.current = 0; // Reset drag distance
              shapeDragStartPos.current = {
                x: e.evt.clientX,
                y: e.evt.clientY,
                imageX: imagePos.x,
                imageY: imagePos.y,
              };

              // Store original positions of all points
              originalPointsPositions.current = initialPoints.map((point) => ({
                x: point.x,
                y: point.y,
                controlPoint1: point.controlPoint1 ? { x: point.controlPoint1.x, y: point.controlPoint1.y } : undefined,
                controlPoint2: point.controlPoint2 ? { x: point.controlPoint2.x, y: point.controlPoint2.y } : undefined,
              }));
            }}
            onMouseEnter={onMouseEnter}
            onMouseLeave={onMouseLeave}
            key={`vector-shape-${initialPoints.length}-${initialPoints.map((p) => p.id).join("-")}`}
          />

          {/* Ghost line - preview from last point to cursor */}
          {selected && !disabled && !disableGhostLine && (
            <GhostLine
              initialPoints={initialPoints}
              cursorPositionRef={cursorPositionRef}
              draggedControlPoint={draggedControlPoint}
              draggedPointIndex={draggedPointIndex}
              isDraggingNewBezier={isDraggingNewBezier}
              isPathClosed={finalIsPathClosed}
              allowClose={allowClose}
              transform={transform}
              fitScale={fitScale}
              maxPoints={maxPoints}
              minPoints={minPoints}
              skeletonEnabled={skeletonEnabled}
              selectedPointIndex={selectedPointIndex}
              lastAddedPointId={lastAddedPointId}
              activePointId={activePointId}
              stroke={stroke}
              pixelSnapping={pixelSnapping}
              drawingDisabled={drawingDisabled}
              isShiftKeyHeld={isShiftKeyHeld}
              transformMode={transformMode}
              effectiveSelectedPointsSize={effectiveSelectedPoints.size}
            />
          )}

          {/* Control points - render first so lines appear under main points */}
          {selected && !disabled && (
            <ControlPoints
              initialPoints={getAllPoints()}
              selectedPointIndex={selectedPointIndex}
              isDraggingNewBezier={isDraggingNewBezier}
              draggedControlPoint={draggedControlPoint}
              visibleControlPoints={visibleControlPoints}
              transform={transform}
              fitScale={fitScale}
              key={`control-points-${initialPoints.length}-${initialPoints.map((p, i) => `${i}-${p.x.toFixed(1)}-${p.y.toFixed(1)}-${p.controlPoint1?.x?.toFixed(1) || "null"}-${p.controlPoint1?.y?.toFixed(1) || "null"}-${p.controlPoint2?.x?.toFixed(1) || "null"}-${p.controlPoint2?.y?.toFixed(1) || "null"}`).join("-")}`}
            />
          )}

          {/* All vector points */}
          <VectorPoints
            initialPoints={getAllPoints()}
            selectedPointIndex={selectedPointIndex}
            selectedPoints={effectiveSelectedPoints}
            transform={transform}
            fitScale={fitScale}
            pointRefs={pointRefs}
            selected={selected}
            disabled={disabled}
            transformMode={transformMode}
            pointRadius={pointRadius}
            pointFill={pointFill}
            pointStroke={pointStroke}
            pointStrokeSelected={pointStrokeSelected}
            pointStrokeWidth={pointStrokeWidth}
            activePointId={activePointId}
            maxPoints={maxPoints}
            onPointClick={(e, pointIndex) => {
              // Handle Alt+click point deletion FIRST (before other checks)
              if (e.evt.altKey && !e.evt.shiftKey && selected) {
                deletePoint(
                  pointIndex,
                  initialPoints,
                  selectedPointIndex,
                  setSelectedPointIndex,
                  setVisibleControlPoints,
                  onPointSelected,
                  onPointRemoved,
                  onPointsChange,
                  setLastAddedPointId,
                  lastAddedPointId,
                );
                pointSelectionHandled.current = true;
                // Stop event propagation to prevent point addition
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                return; // Successfully deleted point
              }

              // Handle Shift+click point conversion (before other checks)
              if (e.evt.shiftKey && !e.evt.altKey && selected && !disabled) {
                if (
                  handleShiftClickPointConversion(e, {
                    initialPoints,
                    transform,
                    fitScale,
                    x,
                    y,
                    allowBezier,
                    pixelSnapping,
                    onPointsChange,
                    onPointEdited,
                    setVisibleControlPoints,
                  } as any)
                ) {
                  pointSelectionHandled.current = true;
                  return; // Successfully converted point
                }
              }

              // Handle cmd/ctrl-click for multi-selection (when selected and not disabled)
              if (selected && !disabled && (e.evt.ctrlKey || e.evt.metaKey) && !e.evt.altKey && !e.evt.shiftKey) {
                // Check if this instance can have selection
                if (!tracker.canInstanceHaveSelection(instanceId)) {
                  return; // Block the selection
                }

                // Check if this point is already selected - if so, deselect it
                if (effectiveSelectedPoints.has(pointIndex)) {
                  const newSelection = new Set(effectiveSelectedPoints);
                  newSelection.delete(pointIndex);
                  tracker.selectPoints(instanceId, newSelection);
                  pointSelectionHandled.current = true;
                  e.evt.stopImmediatePropagation();
                  return;
                }

                // If not deselection, add to multi-selection
                const newSelection = new Set(effectiveSelectedPoints);
                newSelection.add(pointIndex);
                tracker.selectPoints(instanceId, newSelection);
                pointSelectionHandled.current = true;
                e.evt.stopImmediatePropagation();
                return;
              }

              // Handle point selection even when not selected (similar to shape clicks)
              // But never allow selection when disabled
              if (disabled) {
                return;
              }
              if (!selected) {
                // Check if this instance can have selection
                if (!tracker.canInstanceHaveSelection(instanceId)) {
                  return; // Block the selection
                }

                // Check if we're about to close the path - prevent point selection in this case
                if (
                  shouldClosePathOnPointClick(
                    pointIndex,
                    {
                      initialPoints,
                      allowClose,
                      isPathClosed: finalIsPathClosed,
                      skeletonEnabled,
                      activePointId,
                    } as any,
                    e,
                  )
                ) {
                  return; // Block the selection
                }

                // For non-selected mode, still allow point selection
                tracker.selectPoints(instanceId, new Set([pointIndex]));
                pointSelectionHandled.current = true;

                // CRITICAL: For single-point regions, directly call onClick handler so the region can be selected
                // Single-point regions have no segments to click on, so clicking the point must trigger region selection
                const isSinglePointRegion = initialPoints.length === 1;
                if (isSinglePointRegion && !e.evt.altKey && !e.evt.shiftKey && !e.evt.ctrlKey && !e.evt.metaKey) {
                  // Directly call handleClickWithDebouncing to trigger region selection
                  // This works even when selected=false (Group onClick is undefined)
                  handleClickWithDebouncing(e, onClick, onDblClick);
                  return;
                }

                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                return;
              }

              // Handle regular point selection (when selected, not disabled, and not in transform mode)
              if (selected && !disabled && !transformMode) {
                // Check if this instance can have selection
                if (!tracker.canInstanceHaveSelection(instanceId)) {
                  return; // Block the selection
                }

                // Select only this point (single selection for regular click)
                tracker.selectPoints(instanceId, new Set([pointIndex]));
                pointSelectionHandled.current = true;

                // CRITICAL: For single-point regions, directly call onClick handler so the region can be selected
                // Single-point regions have no segments to click on, so clicking the point must trigger region selection
                const isSinglePointRegion = initialPoints.length === 1;
                if (isSinglePointRegion && !e.evt.altKey && !e.evt.shiftKey && !e.evt.ctrlKey && !e.evt.metaKey) {
                  // Directly call handleClickWithDebouncing to trigger region selection
                  // This works even when selected=false (Group onClick is undefined)
                  handleClickWithDebouncing(e, onClick, onDblClick);
                  return;
                }

                // Don't call onClick here - clicking on points should NOT select/unselect the shape
                // Only clicking on segments should select/unselect the shape
                // EXCEPTION: Single-point regions (handled above)
                // Always stop event propagation to prevent the VectorShape onClick handler from firing
                // This prevents the shape from being selected/unselected when clicking on points
                e.evt.stopImmediatePropagation();
                e.evt.stopPropagation();
                e.evt.preventDefault();
                e.cancelBubble = true;
                return;
              }

              // Mark that point selection was handled
              pointSelectionHandled.current = true;
            }}
            onPointDragStart={eventHandlers.handlePointDragStart}
            onPointDragMove={eventHandlers.handlePointDragMove}
            onPointDragEnd={eventHandlers.handlePointDragEnd}
            onPointConvert={eventHandlers.handlePointConvert}
            onControlPointDragStart={eventHandlers.handleControlPointDragStart}
            onControlPointDragMove={eventHandlers.handleControlPointDragMove}
            onControlPointDragEnd={eventHandlers.handleControlPointDragEnd}
            onControlPointConvert={eventHandlers.handleControlPointConvert}
            onSegmentClick={eventHandlers.handleSegmentClick}
            visibleControlPoints={visibleControlPoints}
            allowBezier={allowBezier}
            isTransforming={isTransforming}
            key={`vector-points-${initialPoints.length}-${initialPoints.map((p, i) => `${i}-${p.x.toFixed(1)}-${p.y.toFixed(1)}-${p.controlPoint1?.x?.toFixed(1) || "null"}-${p.controlPoint1?.y?.toFixed(1) || "null"}-${p.controlPoint2?.x?.toFixed(1) || "null"}-${p.controlPoint2?.y?.toFixed(1) || "null"}`).join("-")}`}
          />

          {/* Proxy nodes for Transformer (positioned at exact point centers) - only show when not in drawing mode and not multi-region selected */}
          {drawingDisabled && !isMultiRegionSelected && (
            <ProxyNodes selectedPoints={effectiveSelectedPoints} initialPoints={getAllPoints()} proxyRefs={proxyRefs} />
          )}

          {/* Transformer for multiselection - only show when not in drawing mode and not multi-region selected */}
          {drawingDisabled && !isMultiRegionSelected && (
            <VectorTransformer
              selectedPoints={effectiveSelectedPoints}
              initialPoints={getAllPoints()}
              transformerRef={transformerRef}
              proxyRefs={proxyRefs}
              getCurrentPointsRef={getCurrentPointsRef}
              updateCurrentPointsRef={updateCurrentPointsRef}
              pixelSnapping={pixelSnapping}
              onPointsChange={(newPoints) => {
                // Update main path points
                onPointsChange?.(newPoints);
              }}
              onTransformationComplete={notifyTransformationComplete}
              onTransformationStart={() => {
                handleTransformStart();
              }}
              onTransformationEnd={() => {
                handleTransformEnd();
              }}
              bounds={{ x: 0, y: 0, width, height }}
              transform={transform}
              fitScale={fitScale}
            />
          )}
        </>
      )}

      {/* Ghost point - ALWAYS render if ghostPoint exists, outside any conditionals */}
      <GhostPoint
        ref={ghostPointRef}
        ghostPoint={ghostPoint}
        transform={transform}
        fitScale={fitScale}
        isShiftKeyHeld={isShiftKeyHeld}
        maxPoints={maxPoints}
        initialPointsLength={initialPoints.length}
        isDragging={isDragging.current}
      />
    </Group>
  );
});
